changeset 8215:928bc1d8b279 default-i18n

Merge from default
author Mads Kiilerich <mads@kiilerich.com>
date Thu, 06 Feb 2020 01:19:23 +0100
parents 460e7d2d1b38 (current diff) 7fef5132620c (diff)
children 85fb5e8db79f
files docs/setup.rst kallithea/i18n/be/LC_MESSAGES/kallithea.po kallithea/i18n/bg/LC_MESSAGES/kallithea.po kallithea/i18n/cs/LC_MESSAGES/kallithea.po kallithea/i18n/da/LC_MESSAGES/kallithea.po kallithea/i18n/de/LC_MESSAGES/kallithea.po kallithea/i18n/el/LC_MESSAGES/kallithea.po kallithea/i18n/es/LC_MESSAGES/kallithea.po kallithea/i18n/fr/LC_MESSAGES/kallithea.po kallithea/i18n/hu/LC_MESSAGES/kallithea.po kallithea/i18n/ja/LC_MESSAGES/kallithea.po kallithea/i18n/kallithea.pot kallithea/i18n/lb/LC_MESSAGES/kallithea.po kallithea/i18n/nb_NO/LC_MESSAGES/kallithea.po kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po kallithea/i18n/pl/LC_MESSAGES/kallithea.po kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po kallithea/i18n/ru/LC_MESSAGES/kallithea.po kallithea/i18n/sk/LC_MESSAGES/kallithea.po kallithea/i18n/tr/LC_MESSAGES/kallithea.po kallithea/i18n/uk/LC_MESSAGES/kallithea.po kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po
diffstat 261 files changed, 16752 insertions(+), 11791 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sun Jan 05 01:19:05 2020 +0100
+++ b/.hgtags	Thu Feb 06 01:19:23 2020 +0100
@@ -73,3 +73,6 @@
 60f726162fd6c515bd819feb423be73cad01d7d3 0.4.0rc2
 19086c5de05f4984d7a90cd31624c45dd893f6bb 0.4.0
 da65398a62fff50f3d241796cbf17acdea2092ef 0.4.1
+bfa0b0a814644f0af3f492d17a9ed169cc3b89fe 0.5.0
+d01a8e92936dbd62c76505432f60efba432e9397 0.5.1
+aa0a637fa6f635a5e024fa56b19ed2a2dacca857 0.5.2
--- a/CONTRIBUTORS	Sun Jan 05 01:19:05 2020 +0100
+++ b/CONTRIBUTORS	Thu Feb 06 01:19:23 2020 +0100
@@ -1,15 +1,26 @@
 List of contributors to Kallithea project:
 
+    Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2020
+    Mads Kiilerich <mads@kiilerich.com> 2016-2020
+    Dennis Fink <dennis.fink@c3l.lu> 2020
     Andrej Shadura <andrew@shadura.me> 2012 2014-2017 2019
-    Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2019
     Étienne Gilli <etienne.gilli@gmail.com> 2015-2017 2019
-    Mads Kiilerich <mads@kiilerich.com> 2016-2019
     Allan Nordhøy <epost@anotheragency.no> 2017-2019
     ssantos <ssantos@web.de> 2018-2019
+    Adi Kriegisch <adi@cg.tuwien.ac.at> 2019
     Danni Randeris <danniranderis@gmail.com> 2019
     Edmund Wong <ewong@crazy-cat.org> 2019
+    Elizabeth Sherrock <lizzyd710@gmail.com> 2019
+    Hüseyin Tunç <huseyin.tunc@bulutfon.com> 2019
+    leela <53352@protonmail.com> 2019
     Manuel Jacob <me@manueljacob.de> 2019
+    Mateusz Mendel <mendelm9@gmail.com> 2019
+    Nathan <bonnemainsnathan@gmail.com> 2019
+    Oleksandr Shtalinberg <o.shtalinberg@gmail.com> 2019
+    Private <adamantine.sword@gmail.com> 2019
+    THANOS SIOURDAKIS <siourdakisthanos@gmail.com> 2019
     Wolfgang Scherer <wolfgang.scherer@gmx.de> 2019
+    Христо Станев <hstanev@gmail.com> 2019
     Dominik Ruf <dominikruf@gmail.com> 2012 2014-2018
     Michal Čihař <michal@cihar.com> 2014-2015 2018
     Branko Majic <branko@majic.rs> 2015 2018
--- a/Jenkinsfile	Sun Jan 05 01:19:05 2020 +0100
+++ b/Jenkinsfile	Thu Feb 06 01:19:23 2020 +0100
@@ -9,10 +9,10 @@
                               daysToKeepStr: '',
                               numToKeepStr: '']]]);
     if (isUnix()) {
-        createvirtualenv = 'rm -r $JENKINS_HOME/venv/$JOB_NAME || true && virtualenv $JENKINS_HOME/venv/$JOB_NAME'
+        createvirtualenv = 'rm -r $JENKINS_HOME/venv/$JOB_NAME || true && python3 -m venv $JENKINS_HOME/venv/$JOB_NAME'
         activatevirtualenv = '. $JENKINS_HOME/venv/$JOB_NAME/bin/activate'
     } else {
-        createvirtualenv = 'rmdir /s /q %JENKINS_HOME%\\venv\\%JOB_NAME% || true && virtualenv %JENKINS_HOME%\\venv\\%JOB_NAME%'
+        createvirtualenv = 'rmdir /s /q %JENKINS_HOME%\\venv\\%JOB_NAME% || true && python3 -m venv %JENKINS_HOME%\\venv\\%JOB_NAME%'
         activatevirtualenv = 'call %JENKINS_HOME%\\venv\\%JOB_NAME%\\Scripts\\activate.bat'
     }
 
--- a/MANIFEST.in	Sun Jan 05 01:19:05 2020 +0100
+++ b/MANIFEST.in	Thu Feb 06 01:19:23 2020 +0100
@@ -7,6 +7,7 @@
 include           LICENSE.md
 include           MIT-Permissive-License.txt
 include           README.rst
+include           conftest.py
 include           dev_requirements.txt
 include           development.ini
 include           pytest.ini
--- a/README.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/README.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -24,8 +24,8 @@
 Installation
 ------------
 
-Kallithea requires Python_ 2.7 and it is recommended to install it in a
-virtualenv_. Official releases of Kallithea can be installed with::
+Kallithea requires Python_ 3 and it is recommended to install it in a
+virtualenv. Official releases of Kallithea can be installed with::
 
     pip install kallithea
 
@@ -173,7 +173,6 @@
 of Kallithea.
 
 
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 .. _Python: http://www.python.org/
 .. _Sphinx: http://sphinx.pocoo.org/
 .. _Mercurial: http://mercurial.selenic.com/
--- a/conftest.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/conftest.py	Thu Feb 06 01:19:23 2020 +0100
@@ -2,10 +2,19 @@
 
 import mock
 import pytest
+import tg
 
 
 here = os.path.dirname(__file__)
 
+# HACK:
+def pytest_configure():
+    # Register global dummy tg.context to avoid "TypeError: No object (name: context) has been registered for this thread"
+    tg.request_local.context._push_object(tg.util.bunch.Bunch())
+    # could be removed again after use with
+    # tg.request_local.context._pop_object ... but we keep it around forever as
+    # a reasonable sentinel
+
 def pytest_ignore_collect(path):
     # ignore all files outside the 'kallithea' directory
     if not str(path).startswith(os.path.join(here, 'kallithea')):
@@ -36,3 +45,10 @@
     m = __import__(request.module.__name__, globals(), locals(), [None], 0)
     with mock.patch.object(m, '_', lambda s: s):
         yield
+
+if getattr(pytest, 'register_assert_rewrite', None):
+    # make sure that all asserts under kallithea/tests benefit from advanced
+    # assert reporting with pytest-3.0.0+, including api/api_base.py,
+    # models/common.py etc.
+    # See also: https://docs.pytest.org/en/latest/assert.html#advanced-assertion-introspection
+    pytest.register_assert_rewrite('kallithea.tests')
--- a/dev_requirements.txt	Sun Jan 05 01:19:05 2020 +0100
+++ b/dev_requirements.txt	Thu Feb 06 01:19:23 2020 +0100
@@ -4,5 +4,5 @@
 pytest-localserver >= 0.5.0, < 0.6
 mock >= 3.0.0, < 3.1
 Sphinx >= 1.8.0, < 1.9
-WebTest >= 2.0.3, < 2.1
+WebTest >= 2.0.6, < 2.1
 isort == 4.3.21
--- a/development.ini	Sun Jan 05 01:19:05 2020 +0100
+++ b/development.ini	Thu Feb 06 01:19:23 2020 +0100
@@ -126,7 +126,7 @@
 ## used, which is correct in many cases but for example not when using uwsgi.
 ## If you change this setting, you should reinstall the Git hooks via
 ## Admin > Settings > Remap and Rescan.
-# git_hook_interpreter = /srv/kallithea/venv/bin/python2
+# git_hook_interpreter = /srv/kallithea/venv/bin/python3
 
 ## path to git executable
 git_path = git
--- a/docs/administrator_guide/auth.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/administrator_guide/auth.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -135,10 +135,10 @@
 .. _Custom CA Certificates:
 
 Custom CA Certificates : optional
-    Directory used by OpenSSL to find CAs for validating the LDAP server certificate.
-    Python 2.7.10 and later default to using the system certificate store, and
-    this should thus not be necessary when using certificates signed by a CA
-    trusted by the system.
+    Directory used by OpenSSL to find CAs for validating the LDAP server
+    certificate. It defaults to using the system certificate store, and it
+    should thus not be necessary to specify *Custom CA Certificates* when using
+    certificates signed by a CA trusted by the system.
     It can be set to something like `/etc/openldap/cacerts` on older systems or
     if using self-signed certificates.
 
--- a/docs/conf.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/conf.py	Thu Feb 06 01:19:23 2020 +0100
@@ -47,7 +47,7 @@
 
 # General information about the project.
 project = u'Kallithea'
-copyright = u'2010-2019 by various authors, licensed as GPLv3.'
+copyright = u'2010-2020 by various authors, licensed as GPLv3.'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
--- a/docs/contributing.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/contributing.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -32,7 +32,7 @@
 
         hg clone https://kallithea-scm.org/repos/kallithea
         cd kallithea
-        virtualenv ../kallithea-venv
+        python3 -m venv ../kallithea-venv
         source ../kallithea-venv/bin/activate
         pip install --upgrade pip setuptools
         pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam
@@ -92,8 +92,7 @@
 and the test suite creates repositories in the temporary directory. Linux
 systems with /tmp mounted noexec will thus fail.
 
-You can also use ``tox`` to run the tests with all supported Python versions
-(currently only Python 2.7).
+You can also use ``tox`` to run the tests with all supported Python versions.
 
 When running tests, Kallithea generates a `test.ini` based on template values
 in `kallithea/tests/conftest.py` and populates the SQLite database specified
@@ -199,8 +198,7 @@
 consistency with existing code. Run ``scripts/run-all-cleanup`` before
 committing to ensure some basic code formatting consistency.
 
-We currently only support Python 2.7.x and nothing else. For now we don't care
-about Python 3 compatibility.
+We support Python 3.6 and later.
 
 We try to support the most common modern web browsers. IE9 is still supported
 to the extent it is feasible, IE8 is not.
--- a/docs/index.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/index.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -78,7 +78,6 @@
    dev/dbmigrations
 
 
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 .. _python: http://www.python.org/
 .. _django: http://www.djangoproject.com/
 .. _mercurial: https://www.mercurial-scm.org/
--- a/docs/installation.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/installation.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -35,12 +35,12 @@
 For Debian and Ubuntu, the following command will ensure that a reasonable
 set of dependencies is installed::
 
-    sudo apt-get install build-essential git python-pip python-virtualenv libffi-dev python-dev
+    sudo apt-get install build-essential git libffi-dev python3-dev
 
 For Fedora and RHEL-derivatives, the following command will ensure that a
 reasonable set of dependencies is installed::
 
-    sudo yum install gcc git python-pip python-virtualenv libffi-devel python-devel
+    sudo yum install gcc git libffi-devel python3-devel
 
 .. _installation-source:
 
@@ -48,16 +48,16 @@
 Installation from repository source
 -----------------------------------
 
-To install Kallithea in a virtualenv_ using the stable branch of the development
+To install Kallithea in a virtualenv using the stable branch of the development
 repository, follow the instructions below::
 
         hg clone https://kallithea-scm.org/repos/kallithea -u stable
         cd kallithea
-        virtualenv ../kallithea-venv
+        python3 -m venv ../kallithea-venv
         . ../kallithea-venv/bin/activate
         pip install --upgrade pip setuptools
         pip install --upgrade -e .
-        python2 setup.py compile_catalog   # for translation of the UI
+        python3 setup.py compile_catalog   # for translation of the UI
 
 You can now proceed to :ref:`setup`.
 
@@ -67,18 +67,18 @@
 Installing a released version in a virtualenv
 ---------------------------------------------
 
-It is highly recommended to use a separate virtualenv_ for installing Kallithea.
+It is highly recommended to use a separate virtualenv for installing Kallithea.
 This way, all libraries required by Kallithea will be installed separately from your
 main Python installation and other applications and things will be less
 problematic when upgrading the system or Kallithea.
-An additional benefit of virtualenv_ is that it doesn't require root privileges.
+An additional benefit of virtualenv is that it doesn't require root privileges.
 
-- Assuming you have installed virtualenv_, create a new virtual environment
-  for example, in `/srv/kallithea/venv`, using the virtualenv command::
+- Assuming you have installed virtualenv, create a new virtual environment
+  for example, in `/srv/kallithea/venv`, using the venv command::
 
-    virtualenv /srv/kallithea/venv
+    python3 -m venv /srv/kallithea/venv
 
-- Activate the virtualenv_ in your current shell session and make sure the
+- Activate the virtualenv in your current shell session and make sure the
   basic requirements are up-to-date by running::
 
     . /srv/kallithea/venv/bin/activate
@@ -133,6 +133,3 @@
     pip install --user kallithea
 
 You can now proceed to :ref:`setup`.
-
-
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
--- a/docs/installation_iis.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/installation_iis.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -1,5 +1,7 @@
 .. _installation_iis:
 
+.. warning:: This section is outdated and needs updating for Python 3.
+
 =====================================================================
 Installing Kallithea on Microsoft Internet Information Services (IIS)
 =====================================================================
@@ -66,7 +68,7 @@
 has been generated, it is necessary to run the following command due to the way
 that ISAPI-WSGI is made::
 
-    python2 dispatch.py install
+    python3 dispatch.py install
 
 This accomplishes two things: generating an ISAPI compliant DLL file,
 ``_dispatch.dll``, and installing a script map handler into IIS for the
@@ -119,7 +121,7 @@
 In order to dump output from WSGI using ``win32traceutil`` it is sufficient to
 type the following in a console window::
 
-    python2 -m win32traceutil
+    python3 -m win32traceutil
 
 and any exceptions occurring in the WSGI layer and below (i.e. in the Kallithea
 application itself) that are uncaught, will be printed here complete with stack
--- a/docs/installation_win.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/installation_win.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -1,5 +1,7 @@
 .. _installation_win:
 
+.. warning:: This section is outdated and needs updating for Python 3.
+
 ====================================================
 Installation on Windows (7/Server 2008 R2 and newer)
 ====================================================
@@ -17,18 +19,16 @@
 Step 1 -- Install Python
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-Install Python 2.7.x. Latest version is recommended. If you need another version, they can run side by side.
+Install Python 3. Latest version is recommended. If you need another version, they can run side by side.
 
-.. warning:: Python 3.x is not supported.
-
-- Download Python 2.7.x from http://www.python.org/download/
+- Download Python 3 from http://www.python.org/download/
 - Choose and click on the version
 - Click on "Windows X86-64 Installer" for x64 or "Windows x86 MSI installer" for Win32.
 - Disable UAC or run the installer with admin privileges. If you chose to disable UAC, do not forget to reboot afterwards.
 
-While writing this guide, the latest version was v2.7.9.
+While writing this guide, the latest version was v3.8.1.
 Remember the specific major and minor versions installed, because they will
-be needed in the next step. In this case, it is "2.7".
+be needed in the next step. In this case, it is "3.8".
 
 Step 2 -- Python BIN
 ^^^^^^^^^^^^^^^^^^^^
@@ -42,7 +42,7 @@
   SETX PATH "%PATH%;[your-python-path]" /M
 
 Please substitute [your-python-path] with your Python installation
-path. Typically this is ``C:\\Python27``.
+path. Typically this is ``C:\\Python38``.
 
 Step 3 -- Install pywin32 extensions
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -52,38 +52,14 @@
 
 - Click on "pywin32" folder
 - Click on the first folder (in this case, Build 219, maybe newer when you try)
-- Choose the file ending with ".amd64-py2.x.exe" (".win32-py2.x.exe"
+- Choose the file ending with ".amd64-py3.x.exe" (".win32-py3.x.exe"
   for Win32) where x is the minor version of Python you installed.
   When writing this guide, the file was:
-  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win-amd64-py2.7.exe/download
+  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win-amd64-py3.8.exe/download
   (x64)
-  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win32-py2.7.exe/download
+  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win32-py3.8.exe/download
   (Win32)
 
-Step 4 -- Install pip
-^^^^^^^^^^^^^^^^^^^^^
-
-pip is a package management system for Python. You will need it to install Kallithea and its dependencies.
-
-If you installed Python 2.7.9+, you already have it (as long as you ran the installer with admin privileges or disabled UAC).
-
-If it was not installed or if you are using Python < 2.7.9:
-
-- Go to https://bootstrap.pypa.io
-- Right-click on get-pip.py and choose Saves as...
-- Run "python2 get-pip.py" in the folder where you downloaded get-pip.py (may require admin access).
-
-.. note::
-
-   See http://stackoverflow.com/questions/4750806/how-to-install-pip-on-windows
-   for details and alternative methods.
-
-Note that pip.exe will be placed inside your Python installation's
-Scripts folder, which is likely not on your path. To correct this,
-open a CMD and type::
-
-  SETX PATH "%PATH%;[your-python-path]\Scripts" /M
-
 Step 5 -- Kallithea folder structure
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -108,24 +84,18 @@
    A python virtual environment will allow for isolation between the Python packages of your system and those used for Kallithea.
    It is strongly recommended to use it to ensure that Kallithea does not change a dependency that other software uses or vice versa.
 
-In a command prompt type::
-
-  pip install virtualenv
-
-Virtualenv will now be inside your Python Scripts path (C:\\Python27\\Scripts or similar).
-
 To create a virtual environment, run::
 
-  virtualenv C:\Kallithea\Env
+  python3 -m venv C:\Kallithea\Env
 
 Step 7 -- Install Kallithea
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 In order to install Kallithea, you need to be able to run "pip install kallithea". It will use pip to install the Kallithea Python package and its dependencies.
 Some Python packages use managed code and need to be compiled.
-This can be done on Linux without any special steps. On Windows, you will need to install Microsoft Visual C++ compiler for Python 2.7.
+This can be done on Linux without any special steps. On Windows, you will need to install Microsoft Visual C++ compiler for Python 3.8.
 
-Download and install "Microsoft Visual C++ Compiler for Python 2.7" from http://aka.ms/vcpython27
+Download and install "Microsoft Visual C++ Compiler for Python 3.8" from http://aka.ms/vcpython27
 
 .. note::
   You can also install the dependencies using already compiled Windows binaries packages. A good source of compiled Python packages is http://www.lfd.uci.edu/~gohlke/pythonlibs/. However, not all of the necessary packages for Kallithea are on this site and some are hard to find, so we will stick with using the compiler.
--- a/docs/installation_win_old.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/installation_win_old.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -1,5 +1,7 @@
 .. _installation_win_old:
 
+.. warning:: This section is outdated and needs updating for Python 3.
+
 ==========================================================
 Installation on Windows (XP/Vista/Server 2003/Server 2008)
 ==========================================================
@@ -60,14 +62,11 @@
 Step 2 -- Install Python
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-Install Python 2.7.x x86 version (32-bit). DO NOT USE A 3.x version.
-Download Python 2.7.x from:
+Install Python 3.8.x from:
 http://www.python.org/download/
 
-Choose "Windows Installer" (32-bit version) not "Windows X86-64
-Installer". While writing this guide, the latest version was v2.7.3.
 Remember the specific major and minor version installed, because it will
-be needed in the next step. In this case, it is "2.7".
+be needed in the next step. In this case, it is "3.8".
 
 .. note::
 
@@ -80,17 +79,17 @@
 http://sourceforge.net/projects/pywin32/files/
 
 - Click on "pywin32" folder
-- Click on the first folder (in this case, Build 217, maybe newer when you try)
-- Choose the file ending with ".win32-py2.x.exe" -> x being the minor
+- Click on the first folder (in this case, Build 218, maybe newer when you try)
+- Choose the file ending with ".win32-py3.x.exe" -> x being the minor
   version of Python you installed (in this case, 7)
   When writing this guide, the file was:
-  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/pywin32-217.win32-py2.7.exe/download
+  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py3.8.exe/download
 
   .. note::
 
      64-bit: Download and install the 64-bit version.
      At the time of writing you can find this at:
-     http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download
+     http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py3.8.exe/download
 
 Step 4 -- Python BIN
 ^^^^^^^^^^^^^^^^^^^^
@@ -117,7 +116,7 @@
     SETX PATH "%PATH%;[your-python-path]" /M
 
   Please substitute [your-python-path] with your Python installation path.
-  Typically: C:\\Python27
+  Typically: C:\\Python38
 
 Step 5 -- Kallithea folder structure
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -139,22 +138,10 @@
 Step 6 -- Install virtualenv
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Install Virtual Env for Python
-
-Navigate to: http://www.virtualenv.org/en/latest/index.html#installation
-Right click on "virtualenv.py" file and choose "Save link as...".
-Download to C:\\Kallithea (or whatever you want)
-(the file is located at
-https://raw.github.com/pypa/virtualenv/master/virtualenv.py)
+Create a virtual Python environment in C:\\Kallithea\\Env (or similar). To
+do so, open a CMD (Python Path should be included in Step3), and write::
 
-Create a virtual Python environment in C:\\Kallithea\\Env (or similar). To
-do so, open a CMD (Python Path should be included in Step3), navigate
-where you downloaded "virtualenv.py", and write::
-
-  python2 virtualenv.py C:\Kallithea\Env
-
-(--no-site-packages is now the default behaviour of virtualenv, no need
-to include it)
+  python3 -m venv C:\Kallithea\Env
 
 Step 7 -- Install Kallithea
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
--- a/docs/overview.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/overview.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -12,7 +12,7 @@
 ------------------
 
 **Kallithea** is written entirely in Python_ and requires Python version
-2.7 or higher. Python 3.x is currently not supported.
+3.6 or higher.
 
 Given a Python installation, there are different ways of providing the
 environment for running Python applications. Each of them pretty much
@@ -30,7 +30,7 @@
 - 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
+- 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.
 
@@ -98,7 +98,7 @@
   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/``
+  other package, usually as a ``.../site-packages/Kallithea-X-py3.8.egg/``
   directory with Python files and everything else that is needed.
 
   (``pip install kallithea`` from a source tree will do pretty much the same
@@ -165,7 +165,6 @@
 .. _Python: http://www.python.org/
 .. _Gunicorn: http://gunicorn.org/
 .. _Waitress: http://waitress.readthedocs.org/en/latest/
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 .. _Gearbox: http://turbogears.readthedocs.io/en/latest/turbogears/gearbox.html
 .. _PyPI: https://pypi.python.org/pypi
 .. _Apache httpd: http://httpd.apache.org/
--- a/docs/setup.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/setup.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -557,11 +557,11 @@
       os.chdir('/srv/kallithea/')
 
       import site
-      site.addsitedir("/srv/kallithea/venv/lib/python2.7/site-packages")
+      site.addsitedir("/srv/kallithea/venv/lib/python3.7/site-packages")
 
       ini = '/srv/kallithea/my.ini'
       from logging.config import fileConfig
-      fileConfig(ini)
+      fileConfig(ini, {'__file__': ini, 'here': '/srv/kallithea'})
       from paste.deploy import loadapp
       application = loadapp('config:' + ini)
 
@@ -577,7 +577,7 @@
 
       ini = '/srv/kallithea/kallithea.ini'
       from logging.config import fileConfig
-      fileConfig(ini)
+      fileConfig(ini, {'__file__': ini, 'here': '/srv/kallithea'})
       from paste.deploy import loadapp
       application = loadapp('config:' + ini)
 
@@ -624,7 +624,6 @@
 .. __: https://kallithea-scm.org/repos/kallithea/files/tip/init.d/ .
 
 
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 .. _python: http://www.python.org/
 .. _Python regular expression documentation: https://docs.python.org/2/library/re.html
 .. _Mercurial: https://www.mercurial-scm.org/
--- a/docs/upgrade.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/upgrade.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -241,6 +241,3 @@
 .. note::
     Kallithea does not use hooks on Mercurial repositories. This step is thus
     not necessary if you only have Mercurial repositories.
-
-
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
--- a/docs/usage/performance.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/usage/performance.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -23,6 +23,15 @@
 Tweak beaker cache settings in the ini file. The actual effect of that is
 questionable.
 
+.. note::
+
+    Beaker has no upper bound on cache size and will never drop any caches. For
+    memory cache, the only option is to regularly restart the worker process.
+    For file cache, it must be cleaned manually, as described in the `Beaker
+    documentation <https://beaker.readthedocs.io/en/latest/sessions.html#removing-expired-old-sessions>`_::
+
+        find data/cache -type f -mtime +30 -print -exec rm {} \;
+
 
 Database
 --------
--- a/docs/usage/troubleshooting.rst	Sun Jan 05 01:19:05 2020 +0100
+++ b/docs/usage/troubleshooting.rst	Thu Feb 06 01:19:23 2020 +0100
@@ -8,7 +8,7 @@
 :A: Make sure either to set the ``static_files = true`` in the .ini file or
    double check the root path for your http setup. It should point to
    for example:
-   ``/home/my-virtual-python/lib/python2.7/site-packages/kallithea/public``
+   ``/home/my-virtual-python/lib/python3.7/site-packages/kallithea/public``
 
 |
 
@@ -67,7 +67,6 @@
     you have installed the latest Windows patches (especially KB2789397).
 
 
-.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 .. _python: http://www.python.org/
 .. _mercurial: https://www.mercurial-scm.org/
 .. _celery: http://celeryproject.org/
--- a/kallithea/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,7 +31,10 @@
 import sys
 
 
-VERSION = (0, 4, 99)
+if sys.version_info < (3, 6):
+    raise Exception('Kallithea requires python 3.6 or later')
+
+VERSION = (0, 5, 99)
 BACKENDS = {
     'hg': 'Mercurial repository',
     'git': 'Git repository',
--- a/kallithea/alembic/env.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/alembic/env.py	Thu Feb 06 01:19:23 2020 +0100
@@ -15,6 +15,7 @@
 # Alembic migration environment (configuration).
 
 import logging
+import os
 from logging.config import fileConfig
 
 from alembic import context
@@ -43,7 +44,9 @@
 # stamping during "kallithea-cli db-create"), config_file_name is not available,
 # and loggers are assumed to already have been configured.
 if config.config_file_name:
-    fileConfig(config.config_file_name, disable_existing_loggers=False)
+    fileConfig(config.config_file_name,
+        {'__file__': config.config_file_name, 'here': os.path.dirname(config.config_file_name)},
+        disable_existing_loggers=False)
 
 
 def include_in_autogeneration(object, name, type, reflected, compare_to):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/versions/151b4a4e8c48_db_migration_step_after_93834966ae01_.py	Thu Feb 06 01:19:23 2020 +0100
@@ -0,0 +1,51 @@
+# 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/>.
+
+"""db: migration step after 93834966ae01 dropped non-nullable inherit_default_permissions
+
+Revision ID: 151b4a4e8c48
+Revises: b74907136bc1
+Create Date: 2019-11-23 01:37:42.963119
+
+"""
+
+# The following opaque hexadecimal identifiers ("revisions") are used
+# by Alembic to track this migration script and its relations to others.
+revision = '151b4a4e8c48'
+down_revision = 'b74907136bc1'
+branch_labels = None
+depends_on = None
+
+import sqlalchemy as sa
+from alembic import op
+
+
+def upgrade():
+    meta = sa.MetaData()
+    meta.reflect(bind=op.get_bind())
+
+    if 'inherit_default_permissions' in meta.tables['users'].columns:
+        with op.batch_alter_table('users', schema=None) as batch_op:
+            batch_op.drop_column('inherit_default_permissions')
+
+    if 'users_group_inherit_default_permissions' in meta.tables['users_groups'].columns:
+        with op.batch_alter_table('users_groups', schema=None) as batch_op:
+            batch_op.drop_column('users_group_inherit_default_permissions')
+
+
+def downgrade():
+    with op.batch_alter_table('users_groups', schema=None) as batch_op:
+        batch_op.add_column(sa.Column('users_group_inherit_default_permissions', sa.BOOLEAN(), nullable=False, default=True))
+
+    with op.batch_alter_table('users', schema=None) as batch_op:
+        batch_op.add_column(sa.Column('inherit_default_permissions', sa.BOOLEAN(), nullable=False, default=True))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/versions/4851d15bc437_db_migration_step_after_95c01895c006_.py	Thu Feb 06 01:19:23 2020 +0100
@@ -0,0 +1,51 @@
+# 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/>.
+
+"""db: migration step after 95c01895c006 failed to add usk_public_key_idx in alembic step b74907136bc1
+
+Revision ID: 4851d15bc437
+Revises: 151b4a4e8c48
+Create Date: 2019-11-24 02:51:14.029583
+
+"""
+
+# The following opaque hexadecimal identifiers ("revisions") are used
+# by Alembic to track this migration script and its relations to others.
+revision = '4851d15bc437'
+down_revision = '151b4a4e8c48'
+branch_labels = None
+depends_on = None
+
+import sqlalchemy as sa
+from alembic import op
+
+
+def upgrade():
+    pass
+    # The following upgrade step turned out to be a bad idea. A later step
+    # "d7ec25b66e47_ssh_drop_usk_public_key_idx_again" will remove the index
+    # again if it exists ... but we shouldn't even try to create it.
+
+    #meta = sa.MetaData()
+    #meta.reflect(bind=op.get_bind())
+
+    #if not any(i.name == 'usk_public_key_idx' for i in meta.tables['user_ssh_keys'].indexes):
+    #    with op.batch_alter_table('user_ssh_keys', schema=None) as batch_op:
+    #        batch_op.create_index('usk_public_key_idx', ['public_key'], unique=False)
+
+
+def downgrade():
+    meta = sa.MetaData()
+    if any(i.name == 'usk_public_key_idx' for i in meta.tables['user_ssh_keys'].indexes):
+        with op.batch_alter_table('user_ssh_keys', schema=None) as batch_op:
+            batch_op.drop_index('usk_public_key_idx')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/versions/d7ec25b66e47_ssh_drop_usk_public_key_idx_again.py	Thu Feb 06 01:19:23 2020 +0100
@@ -0,0 +1,43 @@
+# 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/>.
+
+"""ssh: drop usk_public_key_idx again
+
+Revision ID: d7ec25b66e47
+Revises: 4851d15bc437
+Create Date: 2019-12-29 15:33:10.982003
+
+"""
+
+# The following opaque hexadecimal identifiers ("revisions") are used
+# by Alembic to track this migration script and its relations to others.
+revision = 'd7ec25b66e47'
+down_revision = '4851d15bc437'
+branch_labels = None
+depends_on = None
+
+import sqlalchemy as sa
+from alembic import op
+
+
+def upgrade():
+    meta = sa.MetaData()
+    meta.reflect(bind=op.get_bind())
+
+    if any(i.name == 'usk_public_key_idx' for i in meta.tables['user_ssh_keys'].indexes):
+        with op.batch_alter_table('user_ssh_keys', schema=None) as batch_op:
+            batch_op.drop_index('usk_public_key_idx')
+
+
+def downgrade():
+    pass
--- a/kallithea/bin/base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,9 +29,10 @@
 import pprint
 import random
 import sys
-import urllib2
+import urllib.request
 
-from kallithea.lib.compat import json
+from kallithea.lib import ext_json
+from kallithea.lib.utils2 import ascii_bytes
 
 
 CONFIG_NAME = '.config/kallithea'
@@ -67,12 +68,12 @@
         raise Exception('please specify method name !')
     apihost = apihost.rstrip('/')
     id_ = random.randrange(1, 9999)
-    req = urllib2.Request('%s/_admin/api' % apihost,
-                      data=json.dumps(_build_data(id_)),
+    req = urllib.request.Request('%s/_admin/api' % apihost,
+                      data=ascii_bytes(ext_json.dumps(_build_data(id_))),
                       headers={'content-type': 'text/plain'})
-    ret = urllib2.urlopen(req)
+    ret = urllib.request.urlopen(req)
     raw_json = ret.read()
-    json_data = json.loads(raw_json)
+    json_data = ext_json.loads(raw_json)
     id_ret = json_data['id']
     if id_ret == id_:
         return json_data
@@ -107,7 +108,7 @@
     def __getitem__(self, key):
         return self._conf[key]
 
-    def __nonzero__(self):
+    def __bool__(self):
         if self._conf:
             return True
         return False
@@ -128,7 +129,7 @@
         if os.path.exists(self._conf_name):
             update = True
         with open(self._conf_name, 'wb') as f:
-            json.dump(config, f, indent=4)
+            ext_json.dump(config, f, indent=4)
             f.write('\n')
 
         if update:
@@ -146,7 +147,7 @@
         config = {}
         try:
             with open(self._conf_name, 'rb') as conf:
-                config = json.load(conf)
+                config = ext_json.load(conf)
         except IOError as e:
             sys.stderr.write(str(e) + '\n')
 
@@ -159,7 +160,7 @@
         """
         try:
             with open(self._conf_name, 'rb') as conf:
-                return json.load(conf)
+                return ext_json.load(conf)
         except IOError as e:
             #sys.stderr.write(str(e) + '\n')
             pass
--- a/kallithea/bin/kallithea_api.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_api.py	Thu Feb 06 01:19:23 2020 +0100
@@ -28,9 +28,10 @@
 from __future__ import print_function
 
 import argparse
+import json
 import sys
 
-from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call, json
+from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call
 
 
 def argparser(argv):
@@ -101,7 +102,7 @@
         parser.error('Please specify method name')
 
     try:
-        margs = dict(map(lambda s: s.split(':', 1), other))
+        margs = dict(s.split(':', 1) for s in other)
     except ValueError:
         sys.stderr.write('Error parsing arguments \n')
         sys.exit()
--- a/kallithea/bin/kallithea_cli_base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -12,7 +12,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import cStringIO
+import configparser
 import functools
 import logging.config
 import os
@@ -23,6 +23,7 @@
 import paste.deploy
 
 import kallithea
+import kallithea.config.middleware
 
 
 # kallithea_cli is usually invoked through the 'kallithea-cli' wrapper script
@@ -71,11 +72,12 @@
             def runtime_wrapper(config_file, *args, **kwargs):
                 path_to_ini_file = os.path.realpath(config_file)
                 kallithea.CONFIG = paste.deploy.appconfig('config:' + path_to_ini_file)
-                config_bytes = read_config(path_to_ini_file, strip_section_prefix=annotated.__name__)
-                logging.config.fileConfig(cStringIO.StringIO(config_bytes))
+                cp = configparser.ConfigParser(strict=False)
+                cp.read_string(read_config(path_to_ini_file, strip_section_prefix=annotated.__name__))
+                logging.config.fileConfig(cp,
+                    {'__file__': path_to_ini_file, 'here': os.path.dirname(path_to_ini_file)})
                 if config_file_initialize_app:
-                    kallithea.config.middleware.make_app_without_logging(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
-                    kallithea.lib.utils.setup_cache_regions(kallithea.CONFIG)
+                    kallithea.config.middleware.make_app(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
                 return annotated(*args, **kwargs)
             return cli_command(runtime_wrapper)
         return annotator
--- a/kallithea/bin/kallithea_cli_celery.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_celery.py	Thu Feb 06 01:19:23 2020 +0100
@@ -16,6 +16,7 @@
 
 import kallithea
 import kallithea.bin.kallithea_cli_base as cli_base
+from kallithea.lib import celerypylons
 
 
 @cli_base.register_command(config_file_initialize_app=True)
@@ -35,6 +36,6 @@
         raise Exception('Please set use_celery = true in .ini config '
                         'file before running this command')
 
-    from kallithea.lib import celerypylons
-    cmd = celerypylons.worker.worker(celerypylons.app)
+    app = celerypylons.make_app()
+    cmd = celerypylons.worker.worker(app)
     return cmd.run_from_argv(None, command='celery-run -c CONFIG_FILE --', argv=list(celery_args))
--- a/kallithea/bin/kallithea_cli_db.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_db.py	Thu Feb 06 01:19:23 2020 +0100
@@ -67,7 +67,7 @@
     Session().commit()
 
     # initial repository scan
-    kallithea.config.middleware.make_app_without_logging(
+    kallithea.config.middleware.make_app(
             kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
     added, _ = kallithea.lib.utils.repo2db_mapper(kallithea.model.scm.ScmModel().repo_scan())
     if added:
--- a/kallithea/bin/kallithea_cli_iis.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_iis.py	Thu Feb 06 01:19:23 2020 +0100
@@ -33,7 +33,8 @@
 def __ExtensionFactory__():
     from paste.deploy import loadapp
     from logging.config import fileConfig
-    fileConfig('%(inifile)s')
+    fileConfig('%(inifile)s', {'__file__': '%(inifile)s', 'here': '%(inifiledir)s'})
+
     application = loadapp('config:%(inifile)s')
 
     def app(environ, start_response):
@@ -75,6 +76,7 @@
     with open(dispatchfile, 'w') as f:
         f.write(dispath_py_template % {
             'inifile': config_file_abs.replace('\\', '\\\\'),
+            'inifiledir': os.path.dirname(config_file_abs).replace('\\', '\\\\'),
             'virtualdir': virtualdir,
             })
 
--- a/kallithea/bin/kallithea_cli_ishell.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_ishell.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,7 +25,7 @@
 import sys
 
 import kallithea.bin.kallithea_cli_base as cli_base
-from kallithea.model.db import *
+from kallithea.model.db import *  # these names will be directly available in the IPython shell
 
 
 @cli_base.register_command(config_file_initialize_app=True)
--- a/kallithea/bin/kallithea_cli_repo.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_repo.py	Thu Feb 06 01:19:23 2020 +0100
@@ -28,7 +28,7 @@
 
 import kallithea.bin.kallithea_cli_base as cli_base
 from kallithea.lib.utils import REMOVED_REPO_PAT, repo2db_mapper
-from kallithea.lib.utils2 import ask_ok, safe_str, safe_unicode
+from kallithea.lib.utils2 import ask_ok
 from kallithea.model.db import Repository, Ui
 from kallithea.model.meta import Session
 from kallithea.model.scm import ScmModel
@@ -74,7 +74,7 @@
     if not repositories:
         repo_list = Repository.query().all()
     else:
-        repo_names = [safe_unicode(n.strip()) for n in repositories]
+        repo_names = [n.strip() for n in repositories]
         repo_list = list(Repository.query()
                         .filter(Repository.repo_name.in_(repo_names)))
 
@@ -110,7 +110,7 @@
             return
         parts = parts.groupdict()
         time_params = {}
-        for (name, param) in parts.iteritems():
+        for name, param in parts.items():
             if param:
                 time_params[name] = int(param)
         return datetime.timedelta(**time_params)
@@ -127,7 +127,7 @@
 
     repos_location = Ui.get_repos_location()
     to_remove = []
-    for dn_, dirs, f in os.walk(safe_str(repos_location)):
+    for dn_, dirs, f in os.walk(repos_location):
         alldirs = list(dirs)
         del dirs[:]
         if ('.hg' in alldirs or
@@ -175,9 +175,8 @@
         remove = True
     else:
         remove = ask_ok('The following repositories will be removed completely:\n%s\n'
-                'Do you want to proceed? [y/n] '
-                % '\n'.join(['%s deleted on %s' % (safe_str(x[0]), safe_str(x[1]))
-                                     for x in to_remove]))
+            'Do you want to proceed? [y/n] ' %
+            '\n'.join('%s deleted on %s' % (path, date_) for path, date_ in to_remove))
 
     if remove:
         for path, date_ in to_remove:
--- a/kallithea/bin/kallithea_cli_ssh.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_cli_ssh.py	Thu Feb 06 01:19:23 2020 +0100
@@ -14,7 +14,6 @@
 
 import logging
 import os
-import re
 import shlex
 import sys
 
@@ -25,7 +24,7 @@
 from kallithea.lib.utils2 import str2bool
 from kallithea.lib.vcs.backends.git.ssh import GitSshHandler
 from kallithea.lib.vcs.backends.hg.ssh import MercurialSshHandler
-from kallithea.model.ssh_key import SshKeyModel
+from kallithea.model.ssh_key import SshKeyModel, SshKeyModelException
 
 
 log = logging.getLogger(__name__)
@@ -83,5 +82,8 @@
 
     The file is usually maintained automatically, but this command will also re-write it.
     """
-
-    SshKeyModel().write_authorized_keys()
+    try:
+        SshKeyModel().write_authorized_keys()
+    except SshKeyModelException as e:
+        sys.stderr.write("%s\n" % e)
+        sys.exit(1)
--- a/kallithea/bin/kallithea_gist.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/kallithea_gist.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,11 +29,12 @@
 
 import argparse
 import fileinput
+import json
 import os
 import stat
 import sys
 
-from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call, json
+from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call
 
 
 def argparser(argv):
--- a/kallithea/bin/ldap_sync.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/bin/ldap_sync.py	Thu Feb 06 01:19:23 2020 +0100
@@ -27,13 +27,14 @@
 
 from __future__ import print_function
 
-import urllib2
+import urllib.request
 import uuid
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
 
 import ldap
 
-from kallithea.lib.compat import json
+from kallithea.lib import ext_json
+from kallithea.lib.utils2 import ascii_bytes
 
 
 config = ConfigParser()
@@ -80,12 +81,12 @@
         uid = str(uuid.uuid1())
         data = self.get_api_data(uid, method, args)
 
-        data = json.dumps(data)
+        data = ascii_bytes(ext_json.dumps(data))
         headers = {'content-type': 'text/plain'}
-        req = urllib2.Request(self.url, data, headers)
+        req = urllib.request.Request(self.url, data, headers)
 
-        response = urllib2.urlopen(req)
-        response = json.load(response)
+        response = urllib.request.urlopen(req)
+        response = ext_json.load(response)
 
         if uid != response["id"]:
             raise InvalidResponseIDError("UUID does not match.")
--- a/kallithea/config/app_cfg.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/config/app_cfg.py	Thu Feb 06 01:19:23 2020 +0100
@@ -28,20 +28,19 @@
 from alembic.migration import MigrationContext
 from alembic.script.base import ScriptDirectory
 from sqlalchemy import create_engine
-from tg import hooks
 from tg.configuration import AppConfig
 from tg.support.converters import asbool
 
 import kallithea.lib.locale
 import kallithea.model.base
-from kallithea.lib.auth import set_available_permissions
+import kallithea.model.meta
 from kallithea.lib.middleware.https_fixup import HttpsFixup
 from kallithea.lib.middleware.permanent_repo_url import PermanentRepoUrl
 from kallithea.lib.middleware.simplegit import SimpleGit
 from kallithea.lib.middleware.simplehg import SimpleHg
 from kallithea.lib.middleware.wrapper import RequestWrapper
 from kallithea.lib.utils import check_git_version, load_rcextensions, make_ui, set_app_settings, set_indexer_config, set_vcs_config
-from kallithea.lib.utils2 import str2bool
+from kallithea.lib.utils2 import safe_str, str2bool
 
 
 log = logging.getLogger(__name__)
@@ -106,9 +105,6 @@
 
 base_config = KallitheaAppConfig()
 
-# TODO still needed as long as we use pylonslib
-sys.modules['pylons'] = tg
-
 # DebugBar, a debug toolbar for TurboGears2.
 # (https://github.com/TurboGears/tgext.debugbar)
 # To enable it, install 'tgext.debugbar' and 'kajiki', and run Kallithea with
@@ -167,8 +163,7 @@
 
     load_rcextensions(root_path=config['here'])
 
-    set_available_permissions(config)
-    repos_path = make_ui().configitems('paths')[0][1]
+    repos_path = safe_str(make_ui().configitems(b'paths')[0][1])
     config['base_path'] = repos_path
     set_app_settings(config)
 
@@ -188,8 +183,10 @@
 
     check_git_version()
 
+    kallithea.model.meta.Session.remove()
 
-hooks.register('configure_new_app', setup_configuration)
+
+tg.hooks.register('configure_new_app', setup_configuration)
 
 
 def setup_application(app):
@@ -213,4 +210,4 @@
     return app
 
 
-hooks.register('before_config', setup_application)
+tg.hooks.register('before_config', setup_application)
--- a/kallithea/config/conf.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/config/conf.py	Thu Feb 06 01:19:23 2020 +0100
@@ -35,7 +35,7 @@
 # Whoosh index targets
 
 # Extensions we want to index content of using whoosh
-INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
+INDEX_EXTENSIONS = list(LANGUAGES_EXTENSIONS_MAP)
 
 # Filenames we want to index content of using whoosh
 INDEX_FILENAMES = pygmentsutils.get_index_filenames()
--- a/kallithea/config/middleware.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/config/middleware.py	Thu Feb 06 01:19:23 2020 +0100
@@ -13,8 +13,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """WSGI middleware initialization for the Kallithea application."""
 
-import logging.config
-
 from kallithea.config.app_cfg import base_config
 from kallithea.config.environment import load_environment
 
@@ -26,11 +24,6 @@
 make_base_app = base_config.setup_tg_wsgi_app(load_environment)
 
 
-def make_app_without_logging(global_conf, full_stack=True, **app_conf):
-    """The core of make_app for use from gearbox commands (other than 'serve')"""
-    return make_base_app(global_conf, full_stack=full_stack, **app_conf)
-
-
 def make_app(global_conf, full_stack=True, **app_conf):
     """
     Set up Kallithea with the settings found in the PasteDeploy configuration
@@ -49,5 +42,4 @@
     ``app_conf`` contains all the application-specific settings (those defined
     under ``[app:main]``.
     """
-    logging.config.fileConfig(global_conf['__file__'])
-    return make_app_without_logging(global_conf, full_stack=full_stack, **app_conf)
+    return make_base_app(global_conf, full_stack=full_stack, **app_conf)
--- a/kallithea/config/routing.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/config/routing.py	Thu Feb 06 01:19:23 2020 +0100
@@ -19,14 +19,34 @@
 refer to the routes manual at http://routes.groovie.org/docs/
 """
 
-from routes import Mapper
+import routes
 from tg import request
 
+from kallithea.lib.utils2 import safe_str
+
 
 # prefix for non repository related links needs to be prefixed with `/`
 ADMIN_PREFIX = '/_admin'
 
 
+class Mapper(routes.Mapper):
+    """
+    Subclassed Mapper with routematch patched to decode "unicode" str url to
+    *real* unicode str before applying matches and invoking controller methods.
+    """
+
+    def routematch(self, url=None, environ=None):
+        """
+        routematch that also decode url from "fake bytes" to real unicode
+        string before matching and invoking controllers.
+        """
+        # Process url like get_path_info does ... but PATH_INFO has already
+        # been retrieved from environ and is passed, so - let's just use that
+        # instead.
+        url = safe_str(url.encode('latin1'))
+        return super().routematch(url=url, environ=environ)
+
+
 def make_map(config):
     """Create, configure and return the routes Mapper"""
     rmap = Mapper(directory=config['paths']['controllers'],
@@ -86,7 +106,7 @@
     #==========================================================================
 
     # MAIN PAGE
-    rmap.connect('home', '/', controller='home', action='index')
+    rmap.connect('home', '/', controller='home')
     rmap.connect('about', '/about', controller='home', action='about')
     rmap.redirect('/favicon.ico', '/images/favicon.ico')
     rmap.connect('repo_switcher_data', '/_repos', controller='home',
@@ -106,7 +126,7 @@
         m.connect("repos", "/repos",
                   action="create", conditions=dict(method=["POST"]))
         m.connect("repos", "/repos",
-                  action="index", conditions=dict(method=["GET"]))
+                  conditions=dict(method=["GET"]))
         m.connect("new_repo", "/create_repository",
                   action="create_repository", conditions=dict(method=["GET"]))
         m.connect("update_repo", "/repos/{repo_name:.*?}",
@@ -121,7 +141,7 @@
         m.connect("repos_groups", "/repo_groups",
                   action="create", conditions=dict(method=["POST"]))
         m.connect("repos_groups", "/repo_groups",
-                  action="index", conditions=dict(method=["GET"]))
+                  conditions=dict(method=["GET"]))
         m.connect("new_repos_group", "/repo_groups/new",
                   action="new", conditions=dict(method=["GET"]))
         m.connect("update_repos_group", "/repo_groups/{group_name:.*?}",
@@ -161,9 +181,9 @@
         m.connect("new_user", "/users/new",
                   action="create", conditions=dict(method=["POST"]))
         m.connect("users", "/users",
-                  action="index", conditions=dict(method=["GET"]))
+                  conditions=dict(method=["GET"]))
         m.connect("formatted_users", "/users.{format}",
-                  action="index", conditions=dict(method=["GET"]))
+                  conditions=dict(method=["GET"]))
         m.connect("new_user", "/users/new",
                   action="new", conditions=dict(method=["GET"]))
         m.connect("update_user", "/users/{id}",
@@ -216,7 +236,7 @@
         m.connect("users_groups", "/user_groups",
                   action="create", conditions=dict(method=["POST"]))
         m.connect("users_groups", "/user_groups",
-                  action="index", conditions=dict(method=["GET"]))
+                  conditions=dict(method=["GET"]))
         m.connect("new_users_group", "/user_groups/new",
                   action="new", conditions=dict(method=["GET"]))
         m.connect("update_users_group", "/user_groups/{id}",
@@ -263,8 +283,7 @@
     # ADMIN DEFAULTS ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/defaults') as m:
-        m.connect('defaults', '/defaults',
-                  action="index")
+        m.connect('defaults', '/defaults')
         m.connect('defaults_update', 'defaults/{id}/update',
                   action="update", conditions=dict(method=["POST"]))
 
@@ -370,7 +389,7 @@
         m.connect("gists", "/gists",
                   action="create", conditions=dict(method=["POST"]))
         m.connect("gists", "/gists",
-                  action="index", conditions=dict(method=["GET"]))
+                  conditions=dict(method=["GET"]))
         m.connect("new_gist", "/gists/new",
                   action="new", conditions=dict(method=["GET"]))
 
@@ -396,7 +415,7 @@
     # ADMIN MAIN PAGES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/admin') as m:
-        m.connect('admin_home', '', action='index')
+        m.connect('admin_home', '')
         m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9. _-]*}',
                   action='add_repo')
     #==========================================================================
@@ -408,7 +427,7 @@
 
     # USER JOURNAL
     rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
-                 controller='journal', action='index')
+                 controller='journal')
     rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
                  controller='journal', action='journal_rss')
     rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
@@ -475,7 +494,7 @@
     #==========================================================================
     rmap.connect('repo_creating_home', '/{repo_name:.*?}/repo_creating',
                 controller='admin/repos', action='repo_creating')
-    rmap.connect('repo_check_home', '/{repo_name:.*?}/crepo_check',
+    rmap.connect('repo_check_home', '/{repo_name:.*?}/repo_check_creating',
                 controller='admin/repos', action='repo_check')
 
     rmap.connect('summary_home', '/{repo_name:.*?}',
@@ -602,7 +621,7 @@
 
     rmap.connect('compare_home',
                  '/{repo_name:.*?}/compare',
-                 controller='compare', action='index',
+                 controller='compare',
                  conditions=dict(function=check_repo))
 
     rmap.connect('compare_url',
@@ -616,7 +635,7 @@
 
     rmap.connect('pullrequest_home',
                  '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
-                 action='index', conditions=dict(function=check_repo,
+                 conditions=dict(function=check_repo,
                                                  method=["GET"]))
 
     rmap.connect('pullrequest_repo_info',
@@ -674,7 +693,7 @@
                 controller='changelog', conditions=dict(function=check_repo))
 
     rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
-                controller='changelog', f_path=None,
+                controller='changelog',
                 conditions=dict(function=check_repo))
 
     rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
@@ -719,8 +738,8 @@
 
     rmap.connect('files_annotate_home',
                  '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
-                 controller='files', action='index', revision='tip',
-                 f_path='', annotate=True, conditions=dict(function=check_repo))
+                 controller='files', revision='tip',
+                 f_path='', annotate='1', conditions=dict(function=check_repo))
 
     rmap.connect('files_edit_home',
                  '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
--- a/kallithea/controllers/admin/admin.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/admin.py	Thu Feb 06 01:19:23 2020 +0100
@@ -36,7 +36,6 @@
 from whoosh.qparser.dateparse import DateParserPlugin
 from whoosh.qparser.default import QueryParser
 
-from kallithea.config.routing import url
 from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.indexers import JOURNAL_SCHEMA
@@ -61,7 +60,7 @@
     if search_term:
         qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
         qp.add_plugin(DateParserPlugin())
-        qry = qp.parse(unicode(search_term))
+        qry = qp.parse(search_term)
         log.debug('Filtering using parsed query %r', qry)
 
     def wildcard_handler(col, wc_term):
@@ -139,10 +138,8 @@
 
         p = safe_int(request.GET.get('page'), 1)
 
-        def url_generator(**kw):
-            return url.current(filter=c.search_term, **kw)
-
-        c.users_log = Page(users_log, page=p, items_per_page=10, url=url_generator)
+        c.users_log = Page(users_log, page=p, items_per_page=10,
+                           filter=c.search_term)
 
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             return render('admin/admin_log.html')
--- a/kallithea/controllers/admin/defaults.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/defaults.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,7 +31,6 @@
 import formencode
 from formencode import htmlfill
 from tg import request
-from tg import tmpl_context as c
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound
 
@@ -69,7 +68,7 @@
 
         try:
             form_result = _form.to_python(dict(request.POST))
-            for k, v in form_result.iteritems():
+            for k, v in form_result.items():
                 setting = Setting.create_or_update(k, v)
             Session().commit()
             h.flash(_('Default settings updated successfully'),
--- a/kallithea/controllers/admin/gists.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/gists.py	Thu Feb 06 01:19:23 2020 +0100
@@ -40,7 +40,7 @@
 from kallithea.lib.auth import LoginRequired
 from kallithea.lib.base import BaseController, jsonify, render
 from kallithea.lib.page import Page
-from kallithea.lib.utils2 import safe_int, safe_unicode, time_to_datetime
+from kallithea.lib.utils2 import safe_int, safe_str, time_to_datetime
 from kallithea.lib.vcs.exceptions import NodeNotChangedError, VCSError
 from kallithea.model.db import Gist
 from kallithea.model.forms import GistForm
@@ -71,6 +71,11 @@
         not_default_user = not request.authuser.is_default_user
         c.show_private = request.GET.get('private') and not_default_user
         c.show_public = request.GET.get('public') and not_default_user
+        url_params = {}
+        if c.show_public:
+            url_params['public'] = 1
+        elif c.show_private:
+            url_params['private'] = 1
 
         gists = Gist().query() \
             .filter_by(is_expired=False) \
@@ -97,7 +102,8 @@
 
         c.gists = gists
         p = safe_int(request.GET.get('page'), 1)
-        c.gists_pager = Page(c.gists, page=p, items_per_page=10)
+        c.gists_pager = Page(c.gists, page=p, items_per_page=10,
+                             **url_params)
         return render('admin/gists/index.html')
 
     @LoginRequired()
@@ -176,7 +182,10 @@
             log.error(traceback.format_exc())
             raise HTTPNotFound()
         if format == 'raw':
-            content = '\n\n'.join([f.content for f in c.files if (f_path is None or safe_unicode(f.path) == f_path)])
+            content = '\n\n'.join(
+                safe_str(f.content)
+                for f in c.files if (f_path is None or f.path == f_path)
+            )
             response.content_type = 'text/plain'
             return content
         return render('admin/gists/show.html')
--- a/kallithea/controllers/admin/my_account.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/my_account.py	Thu Feb 06 01:19:23 2020 +0100
@@ -279,18 +279,18 @@
             Session().commit()
             SshKeyModel().write_authorized_keys()
             h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
-        except SshKeyModelException as errors:
-            h.flash(errors.message, category='error')
+        except SshKeyModelException as e:
+            h.flash(e.args[0], category='error')
         raise HTTPFound(location=url('my_account_ssh_keys'))
 
     @IfSshEnabled
     def my_account_ssh_keys_delete(self):
-        public_key = request.POST.get('del_public_key')
+        fingerprint = request.POST.get('del_public_key_fingerprint')
         try:
-            SshKeyModel().delete(public_key, request.authuser.user_id)
+            SshKeyModel().delete(fingerprint, request.authuser.user_id)
             Session().commit()
             SshKeyModel().write_authorized_keys()
             h.flash(_("SSH key successfully deleted"), category='success')
-        except SshKeyModelException as errors:
-            h.flash(errors.message, category='error')
+        except SshKeyModelException as e:
+            h.flash(e.args[0], category='error')
         raise HTTPFound(location=url('my_account_ssh_keys'))
--- a/kallithea/controllers/admin/repo_groups.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/repo_groups.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,7 +25,6 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-import itertools
 import logging
 import traceback
 
@@ -37,7 +36,6 @@
 from tg.i18n import ungettext
 from webob.exc import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPNotFound
 
-import kallithea
 from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import HasPermissionAny, HasRepoGroupPermissionLevel, HasRepoGroupPermissionLevelDecorator, LoginRequired
@@ -93,10 +91,8 @@
         return data
 
     def _revoke_perms_on_yourself(self, form_result):
-        _up = filter(lambda u: request.authuser.username == u[0],
-                     form_result['perms_updates'])
-        _new = filter(lambda u: request.authuser.username == u[0],
-                      form_result['perms_new'])
+        _up = [u for u in form_result['perms_updates'] if request.authuser.username == u[0]]
+        _new = [u for u in form_result['perms_new'] if request.authuser.username == u[0]]
         if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
             return True
         return False
@@ -111,18 +107,16 @@
 
         repo_group_name = lambda repo_group_name, children_groups: (
             template.get_def("repo_group_name")
-            .render(repo_group_name, children_groups, _=_, h=h, c=c)
+            .render_unicode(repo_group_name, children_groups, _=_, h=h, c=c)
         )
         repo_group_actions = lambda repo_group_id, repo_group_name, gr_count: (
             template.get_def("repo_group_actions")
-            .render(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
+            .render_unicode(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
                     ungettext=ungettext)
         )
 
         for repo_gr in group_iter:
-            children_groups = map(h.safe_unicode,
-                itertools.chain((g.name for g in repo_gr.parents),
-                                (x.name for x in [repo_gr])))
+            children_groups = [g.name for g in repo_gr.parents] + [repo_gr.name]
             repo_count = repo_gr.repositories.count()
             repo_groups_data.append({
                 "raw_name": repo_gr.group_name,
@@ -148,6 +142,7 @@
         # permissions for can create group based on parent_id are checked
         # here in the Form
         repo_group_form = RepoGroupForm(repo_groups=c.repo_groups)
+        form_result = None
         try:
             form_result = repo_group_form.to_python(dict(request.POST))
             gr = RepoGroupModel().create(
@@ -171,6 +166,8 @@
             log.error(traceback.format_exc())
             h.flash(_('Error occurred during creation of repository group %s')
                     % request.POST.get('group_name'), category='error')
+            if form_result is None:
+                raise
             parent_group_id = form_result['parent_group_id']
             # TODO: maybe we should get back to the main view, not the admin one
             raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id))
--- a/kallithea/controllers/admin/repos.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/repos.py	Thu Feb 06 01:19:23 2020 +0100
@@ -471,7 +471,7 @@
                     category='success')
         except RepositoryError as e:
             log.error(traceback.format_exc())
-            h.flash(str(e), category='error')
+            h.flash(e, category='error')
         except Exception as e:
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during this operation'),
--- a/kallithea/controllers/admin/settings.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/settings.py	Thu Feb 06 01:19:23 2020 +0100
@@ -42,7 +42,7 @@
 from kallithea.lib.celerylib import tasks
 from kallithea.lib.exceptions import HgsubversionImportError
 from kallithea.lib.utils import repo2db_mapper, set_app_settings
-from kallithea.lib.utils2 import safe_unicode
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs import VCSError
 from kallithea.model.db import Repository, Setting, Ui
 from kallithea.model.forms import ApplicationSettingsForm, ApplicationUiSettingsForm, ApplicationVisualisationForm
@@ -168,10 +168,10 @@
                                             user=request.authuser.username,
                                             overwrite_git_hooks=overwrite_git_hooks)
             added_msg = h.HTML(', ').join(
-                h.link_to(safe_unicode(repo_name), h.url('summary_home', repo_name=repo_name)) for repo_name in added
+                h.link_to(safe_str(repo_name), h.url('summary_home', repo_name=repo_name)) for repo_name in added
             ) or '-'
             removed_msg = h.HTML(', ').join(
-                safe_unicode(repo_name) for repo_name in removed
+                safe_str(repo_name) for repo_name in removed
             ) or '-'
             h.flash(h.HTML(_('Repositories successfully rescanned. Added: %s. Removed: %s.')) %
                     (added_msg, removed_msg), category='success')
@@ -423,7 +423,7 @@
         import kallithea
         c.ini = kallithea.CONFIG
         server_info = Setting.get_server_info()
-        for key, val in server_info.iteritems():
+        for key, val in server_info.items():
             setattr(c, key, val)
 
         return htmlfill.render(
--- a/kallithea/controllers/admin/user_groups.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Thu Feb 06 01:19:23 2020 +0100
@@ -32,19 +32,18 @@
 from formencode import htmlfill
 from sqlalchemy.orm import joinedload
 from sqlalchemy.sql.expression import func
-from tg import app_globals, config, request
+from tg import app_globals, request
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound, HTTPInternalServerError
 
-import kallithea
 from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import HasPermissionAnyDecorator, HasUserGroupPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.exceptions import RepoGroupAssignmentError, UserGroupsAssignedException
 from kallithea.lib.utils import action_logger
-from kallithea.lib.utils2 import safe_int, safe_unicode
+from kallithea.lib.utils2 import safe_int, safe_str
 from kallithea.model.db import User, UserGroup, UserGroupRepoGroupToPerm, UserGroupRepoToPerm, UserGroupToPerm
 from kallithea.model.forms import CustomDefaultPermissionsForm, UserGroupForm, UserGroupPermsForm
 from kallithea.model.meta import Session
@@ -61,7 +60,6 @@
     @LoginRequired(allow_default_user=True)
     def _before(self, *args, **kwargs):
         super(UserGroupsController, self)._before(*args, **kwargs)
-        c.available_permissions = config['available_permissions']
 
     def __load_data(self, user_group_id):
         c.group_members_obj = sorted((x.user for x in c.user_group.members),
@@ -94,11 +92,11 @@
 
         user_group_name = lambda user_group_id, user_group_name: (
             template.get_def("user_group_name")
-            .render(user_group_id, user_group_name, _=_, h=h, c=c)
+            .render_unicode(user_group_id, user_group_name, _=_, h=h, c=c)
         )
         user_group_actions = lambda user_group_id, user_group_name: (
             template.get_def("user_group_actions")
-            .render(user_group_id, user_group_name, _=_, h=h, c=c)
+            .render_unicode(user_group_id, user_group_name, _=_, h=h, c=c)
         )
         for user_gr in group_iter:
 
@@ -163,7 +161,7 @@
         c.active = 'settings'
         self.__load_data(id)
 
-        available_members = [safe_unicode(x[0]) for x in c.available_members]
+        available_members = [safe_str(x[0]) for x in c.available_members]
 
         users_group_form = UserGroupForm(edit=True,
                                          old_data=c.user_group.get_dict(),
--- a/kallithea/controllers/admin/users.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/admin/users.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,12 +31,11 @@
 import formencode
 from formencode import htmlfill
 from sqlalchemy.sql.expression import func
-from tg import app_globals, config, request
+from tg import app_globals, request
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound, HTTPNotFound
 
-import kallithea
 from kallithea.config.routing import url
 from kallithea.lib import auth_modules
 from kallithea.lib import helpers as h
@@ -63,7 +62,6 @@
     @HasPermissionAnyDecorator('hg.admin')
     def _before(self, *args, **kwargs):
         super(UsersController, self)._before(*args, **kwargs)
-        c.available_permissions = config['available_permissions']
 
     def index(self, format='html'):
         c.users_list = User.query().order_by(User.username) \
@@ -80,11 +78,11 @@
 
         username = lambda user_id, username: (
                 template.get_def("user_name")
-                .render(user_id, username, _=_, h=h, c=c))
+                .render_unicode(user_id, username, _=_, h=h, c=c))
 
         user_actions = lambda user_id, username: (
                 template.get_def("user_actions")
-                .render(user_id, username, _=_, h=h, c=c))
+                .render_unicode(user_id, username, _=_, h=h, c=c))
 
         for user in c.users_list:
             users_data.append({
@@ -454,20 +452,20 @@
             Session().commit()
             SshKeyModel().write_authorized_keys()
             h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
-        except SshKeyModelException as errors:
-            h.flash(errors.message, category='error')
+        except SshKeyModelException as e:
+            h.flash(e.args[0], category='error')
         raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
 
     @IfSshEnabled
     def ssh_keys_delete(self, id):
         c.user = self._get_user_or_raise_if_default(id)
 
-        public_key = request.POST.get('del_public_key')
+        fingerprint = request.POST.get('del_public_key_fingerprint')
         try:
-            SshKeyModel().delete(public_key, c.user.user_id)
+            SshKeyModel().delete(fingerprint, c.user.user_id)
             Session().commit()
             SshKeyModel().write_authorized_keys()
             h.flash(_("SSH key successfully deleted"), category='success')
-        except SshKeyModelException as errors:
-            h.flash(errors.message, category='error')
+        except SshKeyModelException as e:
+            h.flash(e.args[0], category='error')
         raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
--- a/kallithea/controllers/api/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/api/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -35,11 +35,11 @@
 from tg import Response, TGController, request, response
 from webob.exc import HTTPError, HTTPException
 
+from kallithea.lib import ext_json
 from kallithea.lib.auth import AuthUser
-from kallithea.lib.base import _get_access_path
 from kallithea.lib.base import _get_ip_addr as _get_ip
-from kallithea.lib.compat import json
-from kallithea.lib.utils2 import safe_str, safe_unicode
+from kallithea.lib.base import get_path_info
+from kallithea.lib.utils2 import ascii_bytes
 from kallithea.model.db import User
 
 
@@ -53,7 +53,7 @@
         super(JSONRPCError, self).__init__()
 
     def __str__(self):
-        return safe_str(self.message)
+        return self.message
 
 
 class JSONRPCErrorResponse(Response, HTTPException):
@@ -121,7 +121,7 @@
         raw_body = environ['wsgi.input'].read(length)
 
         try:
-            json_body = json.loads(raw_body)
+            json_body = ext_json.loads(raw_body)
         except ValueError as e:
             # catch JSON errors Here
             raise JSONRPCErrorResponse(retid=self._req_id,
@@ -166,13 +166,13 @@
 
         # now that we have a method, add self._req_params to
         # self.kargs and dispatch control to WGIController
-        argspec = inspect.getargspec(self._func)
-        arglist = argspec[0][1:]
-        defaults = map(type, argspec[3] or [])
-        default_empty = types.NotImplementedType
+        argspec = inspect.getfullargspec(self._func)
+        arglist = argspec.args[1:]
+        argtypes = [type(arg) for arg in argspec.defaults or []]
+        default_empty = type(NotImplemented)
 
         # kw arguments required by this method
-        func_kwargs = dict(itertools.izip_longest(reversed(arglist), reversed(defaults),
+        func_kwargs = dict(itertools.zip_longest(reversed(arglist), reversed(argtypes),
                                                   fillvalue=default_empty))
 
         # This attribute will need to be first param of a method that uses
@@ -180,7 +180,7 @@
         USER_SESSION_ATTR = 'apiuser'
 
         # get our arglist and check if we provided them as args
-        for arg, default in func_kwargs.iteritems():
+        for arg, default in func_kwargs.items():
             if arg == USER_SESSION_ATTR:
                 # USER_SESSION_ATTR is something translated from API key and
                 # this is checked before so we don't need validate it
@@ -209,7 +209,7 @@
 
         log.info('IP: %s Request to %s time: %.3fs' % (
             self._get_ip_addr(environ),
-            safe_unicode(_get_access_path(environ)), time.time() - start)
+            get_path_info(environ), time.time() - start)
         )
 
         state.set_action(self._rpc_call, [])
@@ -226,28 +226,28 @@
             if isinstance(raw_response, HTTPError):
                 self._error = str(raw_response)
         except JSONRPCError as e:
-            self._error = safe_str(e)
+            self._error = str(e)
         except Exception as e:
             log.error('Encountered unhandled exception: %s',
                       traceback.format_exc(),)
             json_exc = JSONRPCError('Internal server error')
-            self._error = safe_str(json_exc)
+            self._error = str(json_exc)
 
         if self._error is not None:
             raw_response = None
 
         response = dict(id=self._req_id, result=raw_response, error=self._error)
         try:
-            return json.dumps(response)
+            return ascii_bytes(ext_json.dumps(response))
         except TypeError as e:
-            log.error('API FAILED. Error encoding response: %s', e)
-            return json.dumps(
+            log.error('API FAILED. Error encoding response for %s %s: %s\n%s', action, rpc_args, e, traceback.format_exc())
+            return ascii_bytes(ext_json.dumps(
                 dict(
                     id=self._req_id,
                     result=None,
-                    error="Error encoding response"
+                    error="Error encoding response",
                 )
-            )
+            ))
 
     def _find_method(self):
         """
--- a/kallithea/controllers/api/api.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/api/api.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1160,7 +1160,7 @@
             return _map[ret_type]
         except KeyError:
             raise JSONRPCError('ret_type must be one of %s'
-                               % (','.join(_map.keys())))
+                               % (','.join(sorted(_map))))
         except Exception:
             log.error(traceback.format_exc())
             raise JSONRPCError(
@@ -2339,7 +2339,7 @@
                                                  branch_name,
                                                  reverse, max_revisions)]
         except EmptyRepositoryError as e:
-            raise JSONRPCError(e.message)
+            raise JSONRPCError('Repository is empty')
 
     # permission check inside
     def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
@@ -2400,7 +2400,7 @@
             pull_request=pull_request.pull_request_id,
             f_path=None,
             line_no=None,
-            status_change=(ChangesetStatus.get_status_lbl(status)),
+            status_change=ChangesetStatus.get_status_lbl(status),
             closing_pr=close_pr
         )
         action_logger(apiuser,
--- a/kallithea/controllers/changelog.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/changelog.py	Thu Feb 06 01:19:23 2020 +0100
@@ -38,8 +38,8 @@
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, render
 from kallithea.lib.graphmod import graph_data
-from kallithea.lib.page import RepoPage
-from kallithea.lib.utils2 import safe_int, safe_str
+from kallithea.lib.page import Page
+from kallithea.lib.utils2 import safe_int
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, EmptyRepositoryError, NodeDoesNotExistError, RepositoryError
 
 
@@ -67,7 +67,7 @@
             h.flash(_('There are no changesets yet'), category='error')
         except RepositoryError as e:
             log.error(traceback.format_exc())
-            h.flash(safe_str(e), category='error')
+            h.flash(e, category='error')
         raise HTTPBadRequest()
 
     @LoginRequired(allow_default_user=True)
@@ -111,35 +111,34 @@
                         cs = self.__get_cs(revision, repo_name)
                         collection = cs.get_file_history(f_path)
                     except RepositoryError as e:
-                        h.flash(safe_str(e), category='warning')
+                        h.flash(e, category='warning')
                         raise HTTPFound(location=h.url('changelog_home', repo_name=repo_name))
-                collection = list(reversed(collection))
             else:
                 collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision,
-                                                        branch_name=branch_name)
+                                                        branch_name=branch_name, reverse=True)
             c.total_cs = len(collection)
 
-            c.cs_pagination = RepoPage(collection, page=p, item_count=c.total_cs,
-                                    items_per_page=c.size, branch=branch_name,)
+            c.cs_pagination = Page(collection, page=p, item_count=c.total_cs, items_per_page=c.size,
+                                   branch=branch_name)
 
             page_revisions = [x.raw_id for x in c.cs_pagination]
             c.cs_comments = c.db_repo.get_comments(page_revisions)
             c.cs_statuses = c.db_repo.statuses(page_revisions)
         except EmptyRepositoryError as e:
-            h.flash(safe_str(e), category='warning')
+            h.flash(e, category='warning')
             raise HTTPFound(location=url('summary_home', repo_name=c.repo_name))
         except (RepositoryError, ChangesetDoesNotExistError, Exception) as e:
             log.error(traceback.format_exc())
-            h.flash(safe_str(e), category='error')
+            h.flash(e, category='error')
             raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name))
 
         c.branch_name = branch_name
         c.branch_filters = [('', _('None'))] + \
-            [(k, k) for k in c.db_repo_scm_instance.branches.keys()]
+            [(k, k) for k in c.db_repo_scm_instance.branches]
         if c.db_repo_scm_instance.closed_branches:
             prefix = _('(closed)') + ' '
             c.branch_filters += [('-', '-')] + \
-                [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
+                [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches]
         revs = []
         if not f_path:
             revs = [x.revision for x in c.cs_pagination]
--- a/kallithea/controllers/changeset.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/changeset.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,6 +25,7 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
+import binascii
 import logging
 import traceback
 from collections import OrderedDict, defaultdict
@@ -40,7 +41,7 @@
 from kallithea.lib.base import BaseRepoController, jsonify, render
 from kallithea.lib.graphmod import graph_data
 from kallithea.lib.utils import action_logger
-from kallithea.lib.utils2 import safe_unicode
+from kallithea.lib.utils2 import ascii_str, safe_str
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError
 from kallithea.model.changeset_status import ChangesetStatusModel
@@ -65,7 +66,7 @@
 
 def get_ignore_ws(fid, GET):
     ig_ws_global = GET.get('ignorews')
-    ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
+    ig_ws = [k for k in GET.getall(fid) if k.startswith('WS')]
     if ig_ws:
         try:
             return int(ig_ws[0].split(':')[-1])
@@ -108,9 +109,9 @@
 def get_line_ctx(fid, GET):
     ln_ctx_global = GET.get('context')
     if fid:
-        ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
+        ln_ctx = [k for k in GET.getall(fid) if k.startswith('C')]
     else:
-        _ln_ctx = filter(lambda k: k.startswith('C'), GET)
+        _ln_ctx = [k for k in GET if k.startswith('C')]
         ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
         if ln_ctx:
             ln_ctx = [ln_ctx]
@@ -256,7 +257,7 @@
     Session().commit()
 
     data = {
-       'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
+       'target_id': h.safeid(request.POST.get('f_path')),
     }
     if comment is not None:
         c.comment = comment
@@ -395,6 +396,8 @@
             c.changeset = c.cs_ranges[0]
             c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
                                      for x in c.changeset.parents])
+            c.changeset_graft_source_hash = ascii_str(c.changeset.extra.get(b'source', b''))
+            c.changeset_transplant_source_hash = ascii_str(binascii.hexlify(c.changeset.extra.get(b'transplant_source', b'')))
         if method == 'download':
             response.content_type = 'text/plain'
             response.content_disposition = 'attachment; filename=%s.diff' \
@@ -402,7 +405,7 @@
             return raw_diff
         elif method == 'patch':
             response.content_type = 'text/plain'
-            c.diff = safe_unicode(raw_diff)
+            c.diff = safe_str(raw_diff)
             return render('changeset/patch_changeset.html')
         elif method == 'raw':
             response.content_type = 'text/plain'
--- a/kallithea/controllers/compare.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/compare.py	Thu Feb 06 01:19:23 2020 +0100
@@ -30,6 +30,7 @@
 import logging
 import re
 
+import mercurial.unionrepo
 from tg import request
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
@@ -42,8 +43,7 @@
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, render
 from kallithea.lib.graphmod import graph_data
-from kallithea.lib.utils2 import safe_int, safe_str
-from kallithea.lib.vcs.utils.hgcompat import unionrepo
+from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes, safe_int
 from kallithea.model.db import Repository
 
 
@@ -97,14 +97,9 @@
         elif alias == 'hg':
             # case two independent repos
             if org_repo != other_repo:
-                try:
-                    hgrepo = unionrepo.makeunionrepository(other_repo.baseui,
-                                                           other_repo.path,
-                                                           org_repo.path)
-                except AttributeError: # makeunionrepository was introduced in Mercurial 4.8 23f2299e9e53
-                    hgrepo = unionrepo.unionrepository(other_repo.baseui,
-                                                       other_repo.path,
-                                                       org_repo.path)
+                hgrepo = mercurial.unionrepo.makeunionrepository(other_repo.baseui,
+                                                       safe_bytes(other_repo.path),
+                                                       safe_bytes(org_repo.path))
                 # all ancestors of other_rev will be in other_repo and
                 # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
 
@@ -112,21 +107,27 @@
             else:
                 hgrepo = other_repo._repo
 
-            ancestors = [hgrepo[ancestor].hex() for ancestor in
-                         hgrepo.revs("id(%s) & ::id(%s)", other_rev, org_rev)]
+            ancestors = [ascii_str(hgrepo[ancestor].hex()) for ancestor in
+                         hgrepo.revs(b"id(%s) & ::id(%s)", ascii_bytes(other_rev), ascii_bytes(org_rev))]
             if ancestors:
                 log.debug("shortcut found: %s is already an ancestor of %s", other_rev, org_rev)
             else:
                 log.debug("no shortcut found: %s is not an ancestor of %s", other_rev, org_rev)
-                ancestors = [hgrepo[ancestor].hex() for ancestor in
-                             hgrepo.revs("heads(::id(%s) & ::id(%s))", org_rev, other_rev)] # FIXME: expensive!
+                ancestors = [ascii_str(hgrepo[ancestor].hex()) for ancestor in
+                             hgrepo.revs(b"heads(::id(%s) & ::id(%s))", ascii_bytes(org_rev), ascii_bytes(other_rev))] # FIXME: expensive!
 
-            other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
-                                     other_rev, org_rev, org_rev)
-            other_changesets = [other_repo.get_changeset(rev) for rev in other_revs]
-            org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
-                                   org_rev, other_rev, other_rev)
-            org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs]
+            other_changesets = [
+                other_repo.get_changeset(rev)
+                for rev in hgrepo.revs(
+                    b"ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
+                    ascii_bytes(other_rev), ascii_bytes(org_rev), ascii_bytes(org_rev))
+            ]
+            org_changesets = [
+                org_repo.get_changeset(ascii_str(hgrepo[rev].hex()))
+                for rev in hgrepo.revs(
+                    b"ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
+                    ascii_bytes(org_rev), ascii_bytes(other_rev), ascii_bytes(other_rev))
+            ]
 
         elif alias == 'git':
             if org_repo != other_repo:
@@ -134,15 +135,15 @@
                 from dulwich.client import SubprocessGitClient
 
                 gitrepo = Repo(org_repo.path)
-                SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
+                SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo)
 
                 gitrepo_remote = Repo(other_repo.path)
-                SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
+                SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote)
 
                 revs = [
-                    x.commit.id
-                    for x in gitrepo_remote.get_walker(include=[other_rev],
-                                                       exclude=[org_rev])
+                    ascii_str(x.commit.id)
+                    for x in gitrepo_remote.get_walker(include=[ascii_bytes(other_rev)],
+                                                       exclude=[ascii_bytes(org_rev)])
                 ]
                 other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
                 if other_changesets:
@@ -155,13 +156,13 @@
                 gitrepo_remote.close()
 
             else:
-                so, se = org_repo.run_git_command(
+                so = org_repo.run_git_command(
                     ['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(
+                so = org_repo.run_git_command(
                     ['merge-base', org_rev, other_rev]
                 )
                 ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]]
@@ -277,7 +278,7 @@
                                       ignore_whitespace=ignore_whitespace,
                                       context=line_context)
 
-        diff_processor = diffs.DiffProcessor(raw_diff or '', diff_limit=diff_limit)
+        diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
         c.limited_diff = diff_processor.limited_diff
         c.file_diff_data = []
         c.lines_added = 0
--- a/kallithea/controllers/error.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/error.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,7 +25,7 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-import cgi
+import html
 import logging
 
 from tg import config, expose, request
@@ -64,8 +64,7 @@
             '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_message = html.escape(request.GET.get('code', str(resp.status)))
             c.error_explanation = self.get_error_explanation(resp.status_int)
         else:
             c.error_message = _('No response')
--- a/kallithea/controllers/feed.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/feed.py	Thu Feb 06 01:19:23 2020 +0100
@@ -32,23 +32,19 @@
 from tg import response
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
-from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
 from kallithea import CONFIG
+from kallithea.lib import feeds
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController
 from kallithea.lib.diffs import DiffProcessor
-from kallithea.lib.utils2 import safe_int, safe_unicode, str2bool
+from kallithea.lib.utils2 import safe_int, safe_str, str2bool
 
 
 log = logging.getLogger(__name__)
 
 
-language = 'en-us'
-ttl = "5"
-
-
 class FeedController(BaseRepoController):
 
     @LoginRequired(allow_default_user=True)
@@ -98,64 +94,41 @@
         desc_msg.extend(changes)
         if str2bool(CONFIG.get('rss_include_diff', False)):
             desc_msg.append('\n\n')
-            desc_msg.append(raw_diff)
+            desc_msg.append(safe_str(raw_diff))
         desc_msg.append('</pre>')
-        return map(safe_unicode, desc_msg)
+        return desc_msg
 
-    def atom(self, repo_name):
-        """Produce an atom-1.0 feed via feedgenerator module"""
+    def _feed(self, repo_name, feeder):
+        """Produce a simple feed"""
 
         @cache_region('long_term', '_get_feed_from_cache')
         def _get_feed_from_cache(*_cache_keys):  # parameters are not really used - only as caching key
-            feed = Atom1Feed(
+            header = dict(
                 title=_('%s %s feed') % (c.site_name, repo_name),
                 link=h.canonical_url('summary_home', repo_name=repo_name),
                 description=_('Changes on %s repository') % repo_name,
-                language=language,
-                ttl=ttl
             )
 
             rss_items_per_page = safe_int(CONFIG.get('rss_items_per_page', 20))
+            entries=[]
             for cs in reversed(list(c.db_repo_scm_instance[-rss_items_per_page:])):
-                feed.add_item(title=self._get_title(cs),
-                              link=h.canonical_url('changeset_home', repo_name=repo_name,
-                                       revision=cs.raw_id),
-                              author_name=cs.author,
-                              description=''.join(self.__get_desc(cs)),
-                              pubdate=cs.date,
-                              )
+                entries.append(dict(
+                    title=self._get_title(cs),
+                    link=h.canonical_url('changeset_home', repo_name=repo_name, revision=cs.raw_id),
+                    author_email=cs.author_email,
+                    author_name=cs.author_name,
+                    description=''.join(self.__get_desc(cs)),
+                    pubdate=cs.date,
+                ))
+            return feeder.render(header, entries)
 
-            response.content_type = feed.mime_type
-            return feed.writeString('utf-8')
+        response.content_type = feeder.content_type
+        return _get_feed_from_cache(repo_name, feeder.__name__)
 
-        kind = 'ATOM'
-        return _get_feed_from_cache(repo_name, kind, c.db_repo.changeset_cache.get('raw_id'))
+    def atom(self, repo_name):
+        """Produce a simple atom-1.0 feed"""
+        return self._feed(repo_name, feeds.AtomFeed)
 
     def rss(self, repo_name):
-        """Produce an rss2 feed via feedgenerator module"""
-
-        @cache_region('long_term', '_get_feed_from_cache')
-        def _get_feed_from_cache(*_cache_keys):  # parameters are not really used - only as caching key
-            feed = Rss201rev2Feed(
-                title=_('%s %s feed') % (c.site_name, repo_name),
-                link=h.canonical_url('summary_home', repo_name=repo_name),
-                description=_('Changes on %s repository') % repo_name,
-                language=language,
-                ttl=ttl
-            )
-
-            rss_items_per_page = safe_int(CONFIG.get('rss_items_per_page', 20))
-            for cs in reversed(list(c.db_repo_scm_instance[-rss_items_per_page:])):
-                feed.add_item(title=self._get_title(cs),
-                              link=h.canonical_url('changeset_home', repo_name=repo_name,
-                                       revision=cs.raw_id),
-                              author_name=cs.author,
-                              description=''.join(self.__get_desc(cs)),
-                              pubdate=cs.date,
-                             )
-
-            response.content_type = feed.mime_type
-            return feed.writeString('utf-8')
-
-        kind = 'RSS'
-        return _get_feed_from_cache(repo_name, kind, c.db_repo.changeset_cache.get('raw_id'))
+        """Produce a simple rss2 feed"""
+        return self._feed(repo_name, feeds.RssFeed)
--- a/kallithea/controllers/files.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/files.py	Thu Feb 06 01:19:23 2020 +0100
@@ -90,7 +90,7 @@
             h.flash(msg, category='error')
             raise HTTPNotFound()
         except RepositoryError as e:
-            h.flash(safe_str(e), category='error')
+            h.flash(e, category='error')
             raise HTTPNotFound()
 
     def __get_filenode(self, cs, path):
@@ -110,7 +110,7 @@
             h.flash(msg, category='error')
             raise HTTPNotFound()
         except RepositoryError as e:
-            h.flash(safe_str(e), category='error')
+            h.flash(e, category='error')
             raise HTTPNotFound()
 
         return file_node
@@ -163,7 +163,7 @@
                 c.load_full_history = False
                 # determine if we're on branch head
                 _branches = c.db_repo_scm_instance.branches
-                c.on_branch_head = revision in _branches.keys() + _branches.values()
+                c.on_branch_head = revision in _branches or revision in _branches.values()
                 _hist = []
                 c.file_history = []
                 if c.load_full_history:
@@ -175,7 +175,7 @@
             else:
                 c.authors = c.file_history = []
         except RepositoryError as e:
-            h.flash(safe_str(e), category='error')
+            h.flash(e, category='error')
             raise HTTPNotFound()
 
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
@@ -232,8 +232,8 @@
         cs = self.__get_cs(revision)
         file_node = self.__get_filenode(cs, f_path)
 
-        response.content_disposition = 'attachment; filename=%s' % \
-            safe_str(f_path.split(Repository.url_sep())[-1])
+        response.content_disposition = \
+            'attachment; filename=%s' % f_path.split(Repository.url_sep())[-1]
 
         response.content_type = file_node.mimetype
         return file_node.content
@@ -277,8 +277,7 @@
                 mimetype, dispo = 'text/plain', 'inline'
 
         if dispo == 'attachment':
-            dispo = 'attachment; filename=%s' % \
-                        safe_str(f_path.split(os.sep)[-1])
+            dispo = 'attachment; filename=%s' % f_path.split(os.sep)[-1]
 
         response.content_disposition = dispo
         response.content_type = mimetype
@@ -292,7 +291,7 @@
         # create multiple heads via file editing
         _branches = repo.scm_instance.branches
         # check if revision is a branch name or branch hash
-        if revision not in _branches.keys() + _branches.values():
+        if revision not in _branches and revision not in _branches.values():
             h.flash(_('You can only delete files with revision '
                       'being a valid branch'), category='warning')
             raise HTTPFound(location=h.url('files_home',
@@ -346,7 +345,7 @@
         # create multiple heads via file editing
         _branches = repo.scm_instance.branches
         # check if revision is a branch name or branch hash
-        if revision not in _branches.keys() + _branches.values():
+        if revision not in _branches and revision not in _branches.values():
             h.flash(_('You can only edit files with revision '
                       'being a valid branch'), category='warning')
             raise HTTPFound(location=h.url('files_home',
@@ -365,8 +364,7 @@
         c.f_path = f_path
 
         if r_post:
-
-            old_content = c.file.content
+            old_content = safe_str(c.file.content)
             sl = old_content.splitlines(1)
             first_line = sl[0] if sl else ''
             # modes:  0 - Unix, 1 - Mac, 2 - DOS
@@ -509,8 +507,7 @@
 
         from kallithea import CONFIG
         rev_name = cs.raw_id[:12]
-        archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
-                                    safe_str(rev_name), ext)
+        archive_name = '%s-%s%s' % (repo_name.replace('/', '_'), rev_name, ext)
 
         archive_path = None
         cached_archive_path = None
--- a/kallithea/controllers/home.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/home.py	Thu Feb 06 01:19:23 2020 +0100
@@ -37,7 +37,7 @@
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseController, jsonify, render
-from kallithea.lib.utils import conditional_cache
+from kallithea.lib.utils2 import safe_str
 from kallithea.model.db import RepoGroup, Repository, User, UserGroup
 from kallithea.model.repo import RepoModel
 from kallithea.model.scm import UserGroupList
@@ -67,9 +67,7 @@
     @LoginRequired(allow_default_user=True)
     @jsonify
     def repo_switcher_data(self):
-        # wrapper for conditional cache
-        def _c():
-            log.debug('generating switcher repo/groups list')
+        if request.is_xhr:
             all_repos = Repository.query(sorted=True).all()
             repo_iter = self.scm_model.get_repos(all_repos)
             all_groups = RepoGroup.query(sorted=True).all()
@@ -96,17 +94,16 @@
                     ],
                    }]
 
+            for res_dict in res:
+                for child in (res_dict['children']):
+                    child['obj'].pop('_changeset_cache', None)  # bytes cannot be encoded in json ... but this value isn't relevant on client side at all ...
+
             data = {
                 'more': False,
                 'results': res,
             }
             return data
 
-        if request.is_xhr:
-            condition = False
-            compute = conditional_cache('short_term', 'cache_desc',
-                                        condition=condition, func=_c)
-            return compute()
         else:
             raise HTTPBadRequest()
 
@@ -120,25 +117,25 @@
         if _branches:
             res.append({
                 'text': _('Branch'),
-                'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches]
+                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'branch'} for name, rev in _branches]
             })
         _closed_branches = repo.closed_branches.items()
         if _closed_branches:
             res.append({
                 'text': _('Closed Branches'),
-                'children': [{'id': rev, 'text': name, 'type': 'closed-branch'} for name, rev in _closed_branches]
+                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'closed-branch'} for name, rev in _closed_branches]
             })
         _tags = repo.tags.items()
         if _tags:
             res.append({
                 'text': _('Tag'),
-                'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags]
+                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'tag'} for name, rev in _tags]
             })
         _bookmarks = repo.bookmarks.items()
         if _bookmarks:
             res.append({
                 'text': _('Bookmark'),
-                'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks]
+                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'book'} for name, rev in _bookmarks]
             })
         data = {
             'more': False,
--- a/kallithea/controllers/journal.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/journal.py	Thu Feb 06 01:19:23 2020 +0100
@@ -23,7 +23,6 @@
 :author: marcink
 :copyright: (c) 2013 RhodeCode GmbH, and others.
 :license: GPLv3, see LICENSE.md for more details.
-
 """
 
 import logging
@@ -35,12 +34,11 @@
 from tg import request, response
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
-from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 from webob.exc import HTTPBadRequest
 
 import kallithea.lib.helpers as h
-from kallithea.config.routing import url
 from kallithea.controllers.admin.admin import _journal_filter
+from kallithea.lib import feeds
 from kallithea.lib.auth import LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.page import Page
@@ -105,22 +103,17 @@
 
         return journal
 
-    def _atom_feed(self, repos, public=True):
+    def _feed(self, repos, feeder, link, desc):
+        response.content_type = feeder.content_type
         journal = self._get_journal_data(repos)
-        if public:
-            _link = h.canonical_url('public_journal_atom')
-            _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')
 
-        feed = Atom1Feed(title=_desc,
-                         link=_link,
-                         description=_desc,
-                         language=language,
-                         ttl=ttl)
+        header = dict(
+            title=desc,
+            link=link,
+            description=desc,
+        )
 
+        entries=[]
         for entry in journal[:feed_nr]:
             user = entry.user
             if user is None:
@@ -131,63 +124,43 @@
             action, action_extra, ico = h.action_parser(entry, feed=True)
             title = "%s - %s %s" % (user.short_contact, action(),
                                     entry.repository.repo_name)
-            desc = action_extra()
             _url = None
             if entry.repository is not None:
                 _url = h.canonical_url('changelog_home',
                            repo_name=entry.repository.repo_name)
 
-            feed.add_item(title=title,
-                          pubdate=entry.action_date,
-                          link=_url or h.canonical_url(''),
-                          author_email=user.email,
-                          author_name=user.full_contact,
-                          description=desc)
+            entries.append(dict(
+                title=title,
+                pubdate=entry.action_date,
+                link=_url or h.canonical_url(''),
+                author_email=user.email,
+                author_name=user.full_name_or_username,
+                description=action_extra(),
+            ))
+
+        return feeder.render(header, entries)
 
-        response.content_type = feed.mime_type
-        return feed.writeString('utf-8')
+    def _atom_feed(self, repos, public=True):
+        if public:
+            link = h.canonical_url('public_journal_atom')
+            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')
+
+        return self._feed(repos, feeds.AtomFeed, link, desc)
 
     def _rss_feed(self, repos, public=True):
-        journal = self._get_journal_data(repos)
         if public:
-            _link = h.canonical_url('public_journal_atom')
-            _desc = '%s %s %s' % (c.site_name, _('Public Journal'),
+            link = h.canonical_url('public_journal_atom')
+            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')
-
-        feed = Rss201rev2Feed(title=_desc,
-                         link=_link,
-                         description=_desc,
-                         language=language,
-                         ttl=ttl)
+            link = h.canonical_url('journal_atom')
+            desc = '%s %s %s' % (c.site_name, _('Journal'), 'rss feed')
 
-        for entry in journal[:feed_nr]:
-            user = entry.user
-            if user is None:
-                # fix deleted users
-                user = AttributeDict({'short_contact': entry.username,
-                                      'email': '',
-                                      'full_contact': ''})
-            action, action_extra, ico = h.action_parser(entry, feed=True)
-            title = "%s - %s %s" % (user.short_contact, action(),
-                                    entry.repository.repo_name)
-            desc = action_extra()
-            _url = None
-            if entry.repository is not None:
-                _url = h.canonical_url('changelog_home',
-                           repo_name=entry.repository.repo_name)
-
-            feed.add_item(title=title,
-                          pubdate=entry.action_date,
-                          link=_url or h.canonical_url(''),
-                          author_email=user.email,
-                          author_name=user.full_contact,
-                          description=desc)
-
-        response.content_type = feed.mime_type
-        return feed.writeString('utf-8')
+        return self._feed(repos, feeds.RssFeed, link, desc)
 
     @LoginRequired()
     def index(self):
@@ -201,10 +174,8 @@
 
         journal = self._get_journal_data(c.following)
 
-        def url_generator(**kw):
-            return url.current(filter=c.search_term, **kw)
-
-        c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
+        c.journal_pager = Page(journal, page=p, items_per_page=20,
+                               filter=c.search_term)
         c.journal_day_aggregate = self._get_daily_aggregate(c.journal_pager)
 
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
@@ -221,9 +192,7 @@
 
     @LoginRequired()
     def journal_atom(self):
-        """
-        Produce an atom-1.0 feed via feedgenerator module
-        """
+        """Produce a simple atom-1.0 feed"""
         following = UserFollowing.query() \
             .filter(UserFollowing.user_id == request.authuser.user_id) \
             .options(joinedload(UserFollowing.follows_repository)) \
@@ -232,9 +201,7 @@
 
     @LoginRequired()
     def journal_rss(self):
-        """
-        Produce an rss feed via feedgenerator module
-        """
+        """Produce a simple rss2 feed"""
         following = UserFollowing.query() \
             .filter(UserFollowing.user_id == request.authuser.user_id) \
             .options(joinedload(UserFollowing.follows_repository)) \
@@ -290,9 +257,7 @@
 
     @LoginRequired(allow_default_user=True)
     def public_journal_atom(self):
-        """
-        Produce an atom-1.0 feed via feedgenerator module
-        """
+        """Produce a simple atom-1.0 feed"""
         c.following = UserFollowing.query() \
             .filter(UserFollowing.user_id == request.authuser.user_id) \
             .options(joinedload(UserFollowing.follows_repository)) \
@@ -302,9 +267,7 @@
 
     @LoginRequired(allow_default_user=True)
     def public_journal_rss(self):
-        """
-        Produce an rss2 feed via feedgenerator module
-        """
+        """Produce a simple rss2 feed"""
         c.following = UserFollowing.query() \
             .filter(UserFollowing.user_id == request.authuser.user_id) \
             .options(joinedload(UserFollowing.follows_repository)) \
--- a/kallithea/controllers/login.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/login.py	Thu Feb 06 01:19:23 2020 +0100
@@ -41,7 +41,6 @@
 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
 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 Setting, User
 from kallithea.model.forms import LoginForm, PasswordResetConfirmationForm, PasswordResetRequestForm, RegisterForm
 from kallithea.model.meta import Session
@@ -68,7 +67,7 @@
         return _re.match(came_from) is not None
 
     def index(self):
-        c.came_from = safe_str(request.GET.get('came_from', ''))
+        c.came_from = request.GET.get('came_from', '')
         if c.came_from:
             if not self._validate_came_from(c.came_from):
                 log.error('Invalid came_from (not server-relative): %r', c.came_from)
@@ -210,12 +209,10 @@
 
         # The template needs the email address outside of the form.
         c.email = request.params.get('email')
-
+        c.timestamp = request.params.get('timestamp') or ''
+        c.token = request.params.get('token') or ''
         if not request.POST:
-            return htmlfill.render(
-                render('/password_reset_confirmation.html'),
-                defaults=dict(request.params),
-                encoding='UTF-8')
+            return render('/password_reset_confirmation.html')
 
         form = PasswordResetConfirmationForm()()
         try:
--- a/kallithea/controllers/pullrequests.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/pullrequests.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,6 +29,7 @@
 import traceback
 
 import formencode
+import mercurial.unionrepo
 from tg import request
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
@@ -42,10 +43,8 @@
 from kallithea.lib.base import BaseRepoController, jsonify, render
 from kallithea.lib.graphmod import graph_data
 from kallithea.lib.page import Page
-from kallithea.lib.utils2 import safe_int
+from kallithea.lib.utils2 import ascii_bytes, safe_bytes, safe_int
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
-from kallithea.lib.vcs.utils import safe_str
-from kallithea.lib.vcs.utils.hgcompat import unionrepo
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.comment import ChangesetCommentsModel
 from kallithea.model.db import ChangesetStatus, PullRequest, PullRequestReviewer, Repository, User
@@ -83,22 +82,15 @@
         # list named branches that has been merged to this named branch - it should probably merge back
         peers = []
 
-        if rev:
-            rev = safe_str(rev)
-
-        if branch:
-            branch = safe_str(branch)
-
         if branch_rev:
-            branch_rev = safe_str(branch_rev)
             # a revset not restricting to merge() would be better
             # (especially because it would get the branch point)
             # ... but is currently too expensive
             # including branches of children could be nice too
             peerbranches = set()
             for i in repo._repo.revs(
-                "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
-                branch_rev, branch_rev
+                b"sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
+                ascii_bytes(branch_rev), ascii_bytes(branch_rev),
             ):
                 for abranch in repo.get_changeset(i).branches:
                     if abranch not in peerbranches:
@@ -111,7 +103,7 @@
         tipbranch = None
 
         branches = []
-        for abranch, branchrev in repo.branches.iteritems():
+        for abranch, branchrev in repo.branches.items():
             n = 'branch:%s:%s' % (abranch, branchrev)
             desc = abranch
             if branchrev == tiprev:
@@ -135,14 +127,14 @@
                 log.debug('branch %r not found in %s', branch, repo)
 
         bookmarks = []
-        for bookmark, bookmarkrev in repo.bookmarks.iteritems():
+        for bookmark, bookmarkrev in repo.bookmarks.items():
             n = 'book:%s:%s' % (bookmark, bookmarkrev)
             bookmarks.append((n, bookmark))
             if rev == bookmarkrev:
                 selected = n
 
         tags = []
-        for tag, tagrev in repo.tags.iteritems():
+        for tag, tagrev in repo.tags.items():
             if tag == 'tip':
                 continue
             n = 'tag:%s:%s' % (tag, tagrev)
@@ -173,7 +165,7 @@
                 if 'master' in repo.branches:
                     selected = 'branch:master:%s' % repo.branches['master']
                 else:
-                    k, v = repo.branches.items()[0]
+                    k, v = list(repo.branches.items())[0]
                     selected = 'branch:%s:%s' % (k, v)
 
         groups = [(specials, _("Special")),
@@ -201,6 +193,11 @@
     def show_all(self, repo_name):
         c.from_ = request.GET.get('from_') or ''
         c.closed = request.GET.get('closed') or ''
+        url_params = {}
+        if c.from_:
+            url_params['from_'] = 1
+        if c.closed:
+            url_params['closed'] = 1
         p = safe_int(request.GET.get('page'), 1)
 
         q = PullRequest.query(include_closed=c.closed, sorted=True)
@@ -210,7 +207,7 @@
             q = q.filter_by(other_repo=c.db_repo)
         c.pull_requests = q.all()
 
-        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100)
+        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100, **url_params)
 
         return render('/pullrequests/pullrequest_show_all.html')
 
@@ -335,7 +332,7 @@
         try:
             cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
         except CreatePullRequestAction.ValidationError as e:
-            h.flash(str(e), category='error', logf=log.error)
+            h.flash(e, category='error', logf=log.error)
             raise HTTPNotFound
 
         try:
@@ -358,7 +355,7 @@
         try:
             cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
         except CreatePullRequestAction.ValidationError as e:
-            h.flash(str(e), category='error', logf=log.error)
+            h.flash(e, category='error', logf=log.error)
             raise HTTPNotFound
 
         try:
@@ -470,14 +467,15 @@
          c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor
 
         org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
-        try:
-            c.cs_ranges = []
-            for x in c.pull_request.revisions:
+        c.cs_ranges = []
+        for x in c.pull_request.revisions:
+            try:
                 c.cs_ranges.append(org_scm_instance.get_changeset(x))
-        except ChangesetDoesNotExistError:
-            c.cs_ranges = []
-            h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
-                'error')
+            except ChangesetDoesNotExistError:
+                c.cs_ranges = []
+                h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
+                    'error')
+                break
         c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
         revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
         c.jsdata = graph_data(org_scm_instance, revs)
@@ -530,14 +528,9 @@
                             # Note: org_scm_instance.path must come first so all
                             # valid revision numbers are 100% org_scm compatible
                             # - both for avail_revs and for revset results
-                            try:
-                                hgrepo = unionrepo.makeunionrepository(org_scm_instance.baseui,
-                                                                       org_scm_instance.path,
-                                                                       other_scm_instance.path)
-                            except AttributeError: # makeunionrepository was introduced in Mercurial 4.8 23f2299e9e53
-                                hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
-                                                                   org_scm_instance.path,
-                                                                   other_scm_instance.path)
+                            hgrepo = mercurial.unionrepo.makeunionrepository(org_scm_instance.baseui,
+                                                                   safe_bytes(org_scm_instance.path),
+                                                                   safe_bytes(other_scm_instance.path))
                         else:
                             hgrepo = org_scm_instance._repo
                         show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
@@ -587,11 +580,11 @@
         log.debug('running diff between %s and %s in %s',
                   c.a_rev, c.cs_rev, org_scm_instance.path)
         try:
-            raw_diff = diffs.get_diff(org_scm_instance, rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
+            raw_diff = diffs.get_diff(org_scm_instance, rev1=c.a_rev, rev2=c.cs_rev,
                                       ignore_whitespace=ignore_whitespace, context=line_context)
         except ChangesetDoesNotExistError:
-            raw_diff = _("The diff can't be shown - the PR revisions could not be found.")
-        diff_processor = diffs.DiffProcessor(raw_diff or '', diff_limit=diff_limit)
+            raw_diff = safe_bytes(_("The diff can't be shown - the PR revisions could not be found."))
+        diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
         c.limited_diff = diff_processor.limited_diff
         c.file_diff_data = []
         c.lines_added = 0
--- a/kallithea/controllers/search.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/search.py	Thu Feb 06 01:19:23 2020 +0100
@@ -27,12 +27,10 @@
 
 import logging
 import traceback
-import urllib
 
 from tg import config, request
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
-from webhelpers2.html.tools import update_params
 from whoosh.index import EmptyIndexError, exists_in, open_dir
 from whoosh.qparser import QueryParser, QueryParserError
 from whoosh.query import Phrase, Prefix
@@ -41,7 +39,7 @@
 from kallithea.lib.base import BaseRepoController, render
 from kallithea.lib.indexers import CHGSET_IDX_NAME, CHGSETS_SCHEMA, IDX_NAME, SCHEMA, WhooshResultWrapper
 from kallithea.lib.page import Page
-from kallithea.lib.utils2 import safe_int, safe_str
+from kallithea.lib.utils2 import safe_int
 from kallithea.model.repo import RepoModel
 
 
@@ -98,7 +96,7 @@
                     # for case-sensitive matching
                     cur_query = u'repository_rawname:%s %s' % (c.repo_name, cur_query)
                 try:
-                    query = qp.parse(unicode(cur_query))
+                    query = qp.parse(cur_query)
                     # extract words for highlight
                     if isinstance(query, Phrase):
                         highlight_items.update(query.words)
@@ -119,9 +117,6 @@
                         res_ln, results.runtime
                     )
 
-                    def url_generator(**kw):
-                        q = urllib.quote(safe_str(c.cur_query))
-                        return update_params("?q=%s&type=%s" % (q, safe_str(c.cur_type)), **kw)
                     repo_location = RepoModel().repos_path
                     c.formated_results = Page(
                         WhooshResultWrapper(search_type, searcher, matcher,
@@ -129,7 +124,8 @@
                         page=p,
                         item_count=res_ln,
                         items_per_page=10,
-                        url=url_generator
+                        type=c.cur_type,
+                        q=c.cur_query,
                     )
 
                 except QueryParserError:
--- a/kallithea/controllers/summary.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/controllers/summary.py	Thu Feb 06 01:19:23 2020 +0100
@@ -38,14 +38,15 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest
 
+import kallithea.lib.helpers as h
 from kallithea.config.conf import ALL_EXTS, ALL_READMES, LANGUAGES_EXTENSIONS_MAP
+from kallithea.lib import ext_json
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, jsonify, render
 from kallithea.lib.celerylib.tasks import get_commits_stats
-from kallithea.lib.compat import json
 from kallithea.lib.markup_renderer import MarkupRenderer
-from kallithea.lib.page import RepoPage
-from kallithea.lib.utils2 import safe_int
+from kallithea.lib.page import Page
+from kallithea.lib.utils2 import safe_int, safe_str
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, NodeDoesNotExistError
 from kallithea.lib.vcs.nodes import FileNode
@@ -83,7 +84,7 @@
                         readme_file = f
                         log.debug('Found README file `%s` rendering...',
                                   readme_file)
-                        readme_data = renderer.render(readme.content,
+                        readme_data = renderer.render(safe_str(readme.content),
                                                       filename=f)
                         break
                     except NodeDoesNotExistError:
@@ -104,8 +105,12 @@
     def index(self, repo_name):
         p = safe_int(request.GET.get('page'), 1)
         size = safe_int(request.GET.get('size'), 10)
-        collection = c.db_repo_scm_instance
-        c.cs_pagination = RepoPage(collection, page=p, items_per_page=size)
+        try:
+            collection = c.db_repo_scm_instance.get_changesets(reverse=True)
+        except EmptyRepositoryError as e:
+            h.flash(e, category='warning')
+            collection = []
+        c.cs_pagination = Page(collection, page=p, items_per_page=size)
         page_revisions = [x.raw_id for x in list(c.cs_pagination)]
         c.cs_comments = c.db_repo.get_comments(page_revisions)
         c.cs_statuses = c.db_repo.statuses(page_revisions)
@@ -133,17 +138,13 @@
         c.stats_percentage = 0
 
         if stats and stats.languages:
-            c.no_data = False is c.db_repo.enable_statistics
-            lang_stats_d = json.loads(stats.languages)
-
+            lang_stats_d = ext_json.loads(stats.languages)
             lang_stats = [(x, {"count": y,
                                "desc": LANGUAGES_EXTENSIONS_MAP.get(x, '?')})
                           for x, y in lang_stats_d.items()]
             lang_stats.sort(key=lambda k: (-k[1]['count'], k[0]))
-
             c.trending_languages = lang_stats[:10]
         else:
-            c.no_data = True
             c.trending_languages = []
 
         c.enable_downloads = c.db_repo.enable_downloads
@@ -171,7 +172,7 @@
             c.no_data_msg = _('Statistics are disabled for this repository')
 
         td = date.today() + timedelta(days=1)
-        td_1m = td - timedelta(days=calendar.mdays[td.month])
+        td_1m = td - timedelta(days=calendar.monthrange(td.year, td.month)[1])
         td_1y = td - timedelta(days=365)
 
         ts_min_m = mktime(td_1m.timetuple())
@@ -185,18 +186,16 @@
             .scalar()
         c.stats_percentage = 0
         if stats and stats.languages:
-            c.no_data = False is c.db_repo.enable_statistics
-            lang_stats_d = json.loads(stats.languages)
-            c.commit_data = json.loads(stats.commit_activity)
-            c.overview_data = json.loads(stats.commit_activity_combined)
+            c.commit_data = ext_json.loads(stats.commit_activity)
+            c.overview_data = ext_json.loads(stats.commit_activity_combined)
 
-            lang_stats = ((x, {"count": y,
-                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
-                          for x, y in lang_stats_d.items())
+            lang_stats_d = ext_json.loads(stats.languages)
+            lang_stats = [(x, {"count": y,
+                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x, '?')})
+                          for x, y in lang_stats_d.items()]
+            lang_stats.sort(key=lambda k: (-k[1]['count'], k[0]))
+            c.trending_languages = lang_stats[:10]
 
-            c.trending_languages = (
-                sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
-            )
             last_rev = stats.stat_on_revision + 1
             c.repo_last_rev = c.db_repo_scm_instance.count() \
                 if c.db_repo_scm_instance.revisions else 0
@@ -208,8 +207,7 @@
         else:
             c.commit_data = {}
             c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]])
-            c.trending_languages = {}
-            c.no_data = True
+            c.trending_languages = []
 
         recurse_limit = 500  # don't recurse more than 500 times when parsing
         get_commits_stats(c.db_repo.repo_name, ts_min_y, ts_max_y, recurse_limit)
--- a/kallithea/i18n/be/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/be/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2017-08-20 10:44+0000\n"
 "Last-Translator: Viktar Vauchkevich <victorenator@gmail.com>\n"
 "Language-Team: Belarusian <https://hosted.weblate.org/projects/kallithea/"
@@ -19,14 +19,14 @@
 "X-Generator: Weblate 2.17-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Яшчэ не было змен"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -35,38 +35,38 @@
 msgid "None"
 msgstr "Нічога"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(зачынена)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Паказваць прабелы"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ігнараваць прабелы"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Павялічыць кантэкст да %(num)s радкоў"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "Няма правоў змяняць статус pull-запыту"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Pull-запыт %s паспяхова выдалены"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Няма такой рэвізіі ў гэтым рэпазітары"
 
@@ -79,48 +79,48 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Немагчыма параўноўваць рэпазітары розных тыпаў"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Немагчыма параўноўваць рэпазітары без агульнага продка"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Няма адказу"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Невядомая памылка"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr "Запыт не распазнаны серверам з-за няправільнага сінтаксісу."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Несанкцыянаваны доступ да рэсурсу"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "У вас няма правоў для прагляду гэтай старонкі"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Рэсурс не знойдзены"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -128,14 +128,14 @@
 "Сервер не можа выканаць запыт з-за нечаканых умоваў, якія ўзніклі падчас "
 "яго спрацавання."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s выканаў каміт у %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -143,12 +143,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Змены апынуліся занадта вялікімі і былі скарочаныя..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "Стужка навін %s %s"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Змены ў рэпазітары %s"
@@ -168,93 +168,93 @@
 msgid "%s at %s"
 msgstr "%s (%s)"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Вы можаце выдаляць файлы толькі ў рэвізіі, злучанай з існай галінай"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Файл %s выдалены з дапамогай Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Файл %s выдалены"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Падчас каміта адбылася памылка"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Вы можаце рэдагаваць файлы толькі ў рэвізіі, злучанай з існай галінай"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Файл %s адрэдагаваны з дапамогай Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Без змен"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Змены захаваныя ў %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Файл дададзены з дапамогай Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Пуста"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Безназоўны"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Размяшчэнне павінна быць адносным шляхам, і не можа ўтрымліваць \"..\" у "
 "шляхі"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Магчымасць спампоўваць адключаная"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Невядомая рэвізія %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Пусты рэпазітар"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Невядомы тып архіва"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Набор змен"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Галіны"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Тэгі"
 
@@ -263,11 +263,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Памылка падчас стварэння форка рэпазітара %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Групы"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -279,204 +279,204 @@
 msgid "Repositories"
 msgstr "Рэпазітары"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Галіна"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Зачыненыя галіны"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Тэгі"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Закладкі"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Публічны журнал"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Журнал"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Няслушная капча"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Рэгістрацыя ў %s прайшла паспяхова"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Код для скідання пароля адпраўлены"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Няслушны код скідання пароля"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Пароль абноўлены"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Няслушны рэцэнзент \"%s\""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (зачынена)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Змены"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Адмысловы"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Галіны ўдзельніка"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Закладкі"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Памылка пры стварэнні pull-запыту: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Адбылася памылка пры стварэнні pull-запыту"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Pull-запыт створаны паспяхова"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "Абнаўленне для pull-запыту створана"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Няма апісання"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Pull-запыт абноўлены"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Pull-запыт паспяхова выдалены"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, fuzzy, python-format
 #| msgid "Changeset for %s %s not found in %s"
 msgid "Revision %s not found in %s"
 msgstr "Набор змен для %s %s не знойдзены ў %s"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "Няма змен для абнаўлення гэтага pull-запыту."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Гэты pull-запыт ужо прыняты на галіну %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "Гэты pull-запыт быў зачынены і не можа быць абноўлены."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, fuzzy, python-format
 #| msgid "The following changes are available on %s:"
 msgid "The following additional changes are available on %s:"
 msgstr "Гэтыя змены даступныя на %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 #, fuzzy
 #| msgid "No changesets found for updating this pull request."
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "Няма змен для абнаўлення гэтага pull-запыту."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Увага: Галіна %s мае яшчэ адну верхавіну: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 #, fuzzy
 #| msgid "Git pull requests don't support updates yet."
 msgid "Git pull requests don't support iterating yet."
 msgstr "Абнаўленне pull-запытаў git яшчэ не падтрымліваецца."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr "Няма змен для абнаўлення гэтага pull-запыту."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Недапушчальны пошукавы запыт. Паспрабуйце скласці яго ў двукоссі."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Памылка пры выкананні гэтага пошуку."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Няма дадзеных"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Статыстычныя дадзеныя адключаны для гэтага рэпазітара"
@@ -489,80 +489,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "памылка пры абнаўленні налад аўтарызацыі"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Стандартныя налады паспяхова абноўлены"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Памылка пры абнаўленні стандартных налад"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Назаўжды"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 хвілін"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 гадзіна"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 дзень"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 месяц"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Тэрмін"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Адбылася памылка падчас стварэння gist-запіса"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist-запіс %s выдалены"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Без змен"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Gist-запіс абноўлены"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Gist-запіс абноўлены"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Памылка пры абнаўленні gist-запісу %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Вы не можаце змяніць дадзеныя гэтага карыстальніка, паколькі ён важны для "
@@ -573,7 +573,7 @@
 msgstr "Ваш уліковы запіс паспяхова абноўлены"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Памылка пры абнаўленні карыстальніка %s"
@@ -583,45 +583,45 @@
 msgstr "Памылка пры абнаўленні пароля"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Карыстальніку дададзены e-mail %s"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Памылка пры захаванні e-mail"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "E-mail карыстальніка выдалены"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API-ключ паспяхова створаны"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "API-ключ паспяхова скінуты"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API-ключ паспяхова выдалены"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "API-ключ паспяхова створаны"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -699,11 +699,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Дазволена, з аўтаматычнай актывацыяй уліковага запісу"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Ручная актывацыя вонкавага ўліковага запісу"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Аўтаматычная актывацыя вонкавага ўліковага запісу"
 
@@ -725,59 +725,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Адбылася памылка падчас абнаўлення прывілеяў"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Адбылася памылка пры стварэнні групы рэпазітароў %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Створаная новая група рэпазітароў %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Група рэпазітароў %s абноўленая"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Адбылася памылка пры абнаўленні групы рэпазітароў %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Група ўтрымлівае %s рэпазітароў і не можа быць выдаленая"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Група ўтрымлівае ў сабе %s падгруп і не можа быць выдаленая"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Група рэпазітароў %s выдаленая"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Памылка пры выдаленні групы рэпазітароў %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Адміністратар не можа адклікаць свае прывелеі"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Прывілеі групы рэпазітароў абноўленыя"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Памылка пры водгуку прывелея"
 
@@ -904,7 +904,7 @@
 msgid "Updated VCS settings"
 msgstr "Абноўлены налады VCS"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -973,97 +973,97 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Запланаванае пераіндэксаванне базы Whoosh"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Створана група карыстальнікаў %s"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Памылка пры стварэнні групы карыстальнікаў %s"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Група карыстальнікаў %s абноўленая"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Памылка пры абнаўленні групы карыстальнікаў %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Група карыстальнікаў паспяхова выдаленая"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Памылка пры выдаленні групы карыстальнікаў"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "Мэтавая група не можа быць той жа самай"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Прывілеі групы карыстальнікаў абноўленыя"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Абноўленыя прывілеі"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Памылка пры захаванні прывілеяў"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Карыстальнік %s створаны"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Памылка пры стварэнні карыстальніка %s"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Карыстальнік паспяхова абноўлены"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Карыстальнік паспяхова выдалены"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Памылка пры выдаленні карыстальніка"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Дададзены IP %s у белы спіс карыстальніка"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Адбылася памылка пры захаванні IP"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "Выдалены IP %s з белага спісу карыстальніка"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 "Вы павінны быць зарэгістраваным карыстальнікам, каб выканаць гэта дзеянне"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Старонка даступная толькі аўтарызаваным карыстальнікам"
 
@@ -1098,170 +1098,170 @@
 "Набор змены апынуўся занадта вялікімі і быў падрэзаны, выкарыстоўвайце "
 "меню параўнання для паказу выніку параўнання"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Змен не выяўлена"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Выдаленая галіна: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Створаны тэг: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Набор змен %s не знойдзены"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Паказаць адрозненні разам %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Параўнанне"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "і"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "на %s больш"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "версіі"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Імя форка %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull-запыт %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[выдалены] рэпазітар"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[створаны] рэпазітар"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[створаны] рэпазітар як форк"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[форкнуты] рэпазітар"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[абноўлены] рэпазітар"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[загружаны] архіў з рэпазітара"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[выдалены] рэпазітар"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[створаны] карыстальнік"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[абноўлены] карыстальнік"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[створана] група карыстальнікаў"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[абноўлена] група карыстальнікаў"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[каментар] да рэвізіі ў рэпазітары"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[каментар] у pull-запыце для"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[зачынены] pull-запыт для"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[адпраўлена] у"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[каміт праз Kallithea] у рэпазітары"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[занесены з аддаленага рэпазітара] у рэпазітар"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[занесены] з"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[дададзены ў назіранні] рэпазітар"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[выдалены з назірання] рэпазітар"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " і на %s больш"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Няма файлаў"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "новы файл"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "зменены"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "выдалены"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "пераназваны"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1272,34 +1272,36 @@
 "пераназваны з файлавай сістэмы. Калі ласка, перазапусціце прыкладанне для "
 "сканавання рэпазітароў"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1307,7 +1309,7 @@
 msgstr[1] "%d гады"
 msgstr[2] "%d гадоў"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1315,7 +1317,7 @@
 msgstr[1] "%d месяцы"
 msgstr[2] "%d месяцаў"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1323,7 +1325,7 @@
 msgstr[1] "%d дні"
 msgstr[2] "%d дзён"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1331,7 +1333,7 @@
 msgstr[1] "%d гадзіны"
 msgstr[2] "%d гадзін"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1339,7 +1341,7 @@
 msgstr[1] "%d хвіліны"
 msgstr[2] "%d хвілін"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1347,27 +1349,27 @@
 msgstr[1] "%d секунды"
 msgstr[2] "%d секунд"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "у %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s назад"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "у %s і %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s і %s назад"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "цяпер"
 
@@ -1376,137 +1378,137 @@
 msgid "on line %s"
 msgstr "на радку %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Згадванне]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "верхні ўзровень"
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr "Адміністратар Kallithea"
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
-msgstr "Адміністратар Kallithea"
-
-#: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "Несанкцыянаваны доступ да рэсурсу"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "Несанкцыянаваны доступ да рэсурсу"
 
+#: kallithea/model/db.py:1639
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1641
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr ""
+msgid "Only admins can create repository groups"
+msgstr "Толькі адміністратары могуць ствараць групы репазітароў"
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
-msgstr ""
+msgid "Non-admins can create repository groups"
+msgstr "Неадміністратары могуць ствараць групы репазітароў"
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
-msgstr "Толькі адміністратары могуць ствараць групы репазітароў"
+msgid "Only admins can create user groups"
+msgstr "Толькі адміністратары могуць ствараць групы карыстальнікаў"
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
-msgstr "Неадміністратары могуць ствараць групы репазітароў"
+msgid "Non-admins can create user groups"
+msgstr "Неадміністратары могуць ствараць групы карыстальнікаў"
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
-msgstr "Толькі адміністратары могуць ствараць групы карыстальнікаў"
+msgid "Only admins can create top level repositories"
+msgstr "Толькі адміністратары могуць ствараць рэпазітары верхняга ўзроўню"
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
-msgstr "Неадміністратары могуць ствараць групы карыстальнікаў"
-
-#: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
-msgstr "Толькі адміністратары могуць ствараць рэпазітары верхняга ўзроўню"
-
-#: kallithea/model/db.py:1661
 msgid "Non-admins can create top level repositories"
 msgstr "Неадміністратары могуць ствараць рэпазітары верхняга ўзроўню"
 
-#: kallithea/model/db.py:1663
+#: kallithea/model/db.py:1660
 msgid ""
 "Repository creation enabled with write permission to a repository group"
 msgstr ""
 
-#: kallithea/model/db.py:1664
+#: kallithea/model/db.py:1661
 msgid ""
 "Repository creation disabled with write permission to a repository group"
 msgstr ""
 
+#: kallithea/model/db.py:1663
+msgid "Only admins can fork repositories"
+msgstr "Месцазнаходжанне рэпазітароў"
+
+#: kallithea/model/db.py:1664
+msgid "Non-admins can fork repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
-msgstr "Месцазнаходжанне рэпазітароў"
+msgid "Registration disabled"
+msgstr "Рэгістрацыя адключаная"
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr "Рэгістрацыя адключаная"
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr "Рэгістрацыя карыстальніка з ручной актывацыяй уліковага запісу"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr "Рэгістрацыя карыстальніка з аўтаматычнай актывацыяй"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "Не прагледжана"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "На разглядзе"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 #, fuzzy
 #| msgid "Approved"
 msgid "Not approved"
 msgstr "Ухвалена"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Ухвалена"
 
@@ -1532,7 +1534,7 @@
 msgid "Name must not contain only digits"
 msgstr "Імя не можа ўтрымліваць толькі лічбы"
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, fuzzy, python-format
 #| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
 msgid ""
@@ -1540,72 +1542,72 @@
 "%(branch)s"
 msgstr "[пракаментавана] у запыце на занясенне змен для"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Новы карыстальнік \"%(new_username)s\" зарэгістраваны"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "Зачынены"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 "%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: %(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Error creating pull request: %s"
 msgid "Cannot create empty pull request"
 msgstr "Памылка пры стварэнні pull-запыту: %s"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 #, fuzzy
 #| msgid "Confirm to delete this pull request"
 msgid "You are not authorized to create the pull request"
 msgstr "Пацвердзіце выдаленне гэтага pull-request'а"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 #, fuzzy
 #| msgid "Missing changesets since the previous pull request:"
 msgid "Missing changesets since the previous iteration:"
 msgstr "Адсутныя рэвізіі адносна папярэдняга pull-запыту:"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, fuzzy, python-format
 #| msgid "New changesets on %s %s since the previous pull request:"
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "Новыя рэвізіі на %s %s адносна папярэдняга pull-запыту:"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, fuzzy, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
@@ -1613,49 +1615,49 @@
 msgstr ""
 "Гэты pull-запыт заснаваны на іншай рэвізіі %s, просты diff немагчымы."
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, fuzzy, python-format
 #| msgid "No changes found on %s %s since previous version."
 msgid "No changes found on %s %s since previous iteration."
 msgstr "Няма змен на %s %s адносна папярэдняй версіі."
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "апошняя версія"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset %s not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Набор змен %s не знойдзены"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "Рэгістрацыя новага карыстальніка"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Вы не можаце выдаліць карыстальніка, паколькі гэта крытычна для працы "
 "ўсёй праграмы"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1665,7 +1667,7 @@
 "таму не можа быць выдалены. Змяніце ўладальніка ці выдаліце гэтыя "
 "рэпазітары: %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1675,7 +1677,7 @@
 "і таму не можа быць выдалены. Змяніце ўладальніка ці выдаліце гэтая "
 "групы: %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1685,15 +1687,15 @@
 "карыстальнікаў і таму не можа быць выдалены. Змяніце ўладальніка ці "
 "выдаліце гэтыя групы: %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "Спасылка скіду пароля"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr "Паведамленне пра скіданне пароля"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2403,7 +2405,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3490,7 +3492,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "Захаваць налады"
 
@@ -3738,13 +3740,13 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repository Size"
 msgid "Repository page size"
 msgstr "Памер рэпазітара"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 #, fuzzy
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
@@ -3753,43 +3755,43 @@
 "Колькасць элементаў, што паказваюцца на галоўнай старонцы панэлі "
 "кіравання перад паказам нумарацыі старонак."
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Абразкі"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Паказваць абразкі публічных рэпазітароў"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Паказваць абразкі прыватных рэпазітароў"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr "Паказваць абразкі публічных рэпазітароў."
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr "Метатэгаванне"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4431,23 +4433,23 @@
 msgid "Merge"
 msgstr "звесці"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr "Перанесена з:"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr "Заменена:"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr "Замяняе:"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4457,7 +4459,7 @@
 msgstr[1] "%s файлы зменена"
 msgstr[2] "%s файлаў зменена"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4467,8 +4469,8 @@
 msgstr[1] "%s файлы зменена: %s даданні, %s выдаленні"
 msgstr[2] "%s файлаў зменена: %s даданняў, %s выдаленняў"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5477,45 +5479,45 @@
 msgid "Stats gathered: "
 msgstr "Атрыманая статыстыка: "
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "файлы"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Паказаць яшчэ"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "commit'ы"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "файлы дададзены"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "файлы зменены"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "файлы выдалены"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "файлы выдалены"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "файл выдалены"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "файл зменены"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "файл выдалены"
 
--- a/kallithea/i18n/bg/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/bg/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.4.99\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -18,14 +18,14 @@
 "Generated-By: Babel 2.6.0\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,36 +34,36 @@
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -76,61 +76,61 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -138,12 +138,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
@@ -161,90 +161,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr ""
 
+#: kallithea/controllers/files.py:502
+#, python-format
+msgid "Unknown revision %s"
+msgstr ""
+
 #: kallithea/controllers/files.py:504
-#, python-format
-msgid "Unknown revision %s"
+msgid "Empty repository"
 msgstr ""
 
 #: kallithea/controllers/files.py:506
-msgid "Empty repository"
-msgstr ""
-
-#: kallithea/controllers/files.py:508
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr ""
 
@@ -253,11 +253,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -269,194 +269,194 @@
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -469,80 +469,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -551,7 +551,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -561,44 +561,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr ""
 
@@ -674,11 +674,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -700,59 +700,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -878,7 +878,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -943,96 +943,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1063,170 +1063,170 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1234,96 +1234,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1332,133 +1334,133 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1484,145 +1486,145 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2311,7 +2313,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3354,7 +3356,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3594,53 +3596,53 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4268,23 +4270,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4293,7 +4295,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4302,8 +4304,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5263,45 +5265,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/cs/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/cs/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2015-11-12 08:51+0000\n"
 "Last-Translator: Michal Čihař <michal@cihar.com>\n"
 "Language-Team: Czech <https://hosted.weblate.org/projects/kallithea/"
@@ -18,14 +18,14 @@
 "X-Generator: Weblate 2.5-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,37 +34,37 @@
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(zavřeno)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 msgid "No permission to change status"
 msgstr "Změny"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, fuzzy, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Úspěšně aktualizované heslo"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Taková revize neexistuje"
 
@@ -78,62 +78,62 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "Neznámá revize %s"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Nemáte oprávnění k zobrazení této stránky"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -141,12 +141,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Změny na repozitáři %s"
@@ -166,90 +166,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Žádné změny"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Přidaný soubor přes Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Žádný obsah"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Stahování vypnuto"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Neznámá revize %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Změny"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Větve"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Tagy"
 
@@ -258,11 +258,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Skupiny"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -274,194 +274,194 @@
 msgid "Repositories"
 msgstr "Repozitáře"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Větev"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Tag"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Záložka"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Špatná captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Úspěšně aktualizované heslo"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (zavřené)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Záložky"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Došlo k chybě při vyhledávání."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -474,80 +474,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 minut"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 hodina"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 den"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 měsíc"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Došlo k chybě při vytváření gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -556,7 +556,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -566,44 +566,44 @@
 msgstr "Došlo k chybě při aktualizaci hesla uživatele"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Došlo k chybě při ukládání e-mailové adresy"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 msgid "SSH key successfully deleted"
 msgstr "Úspěšně aktualizované heslo"
@@ -680,11 +680,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -706,59 +706,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -885,7 +885,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -951,96 +951,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1071,171 +1071,171 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "Změny"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1243,34 +1243,36 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1278,7 +1280,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1286,7 +1288,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1294,7 +1296,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1302,7 +1304,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1310,7 +1312,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1318,27 +1320,27 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1347,135 +1349,135 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1666
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Chyba při vytváření repozitáře %s"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "Chyba při vytváření repozitáře %s"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1501,146 +1503,146 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Set changeset status"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Změny"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2332,7 +2334,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3397,7 +3399,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 #, fuzzy
 msgid "Save Settings"
 msgstr "Nastavení"
@@ -3639,56 +3641,56 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repositories"
 msgid "Repository page size"
 msgstr "Repozitáře"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Nastavení"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4320,23 +4322,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4346,7 +4348,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4356,8 +4358,8 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5344,45 +5346,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/da/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/da/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3.99\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-03-14 01:03+0000\n"
 "Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
 "Language-Team: Danish <https://hosted.weblate.org/projects/kallithea/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.5.1\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Der er ingen changesets endnu"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,38 +33,38 @@
 msgid "None"
 msgstr "Ingen"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(lukket)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Vis mellemrum"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorer mellemrum"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Øg diff konteksten med %(num)s linjer"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "Ingen tilladelse til ændring af status for pull-forespørgsel"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Pull-forespørgsel %s slettet successfuldt"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "En sådan revision findes ikke for dette repository"
 
@@ -77,50 +77,50 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Kan ikke sammenligne repositories af forskellige typer"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "Kan ikke vise en tom diff"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr "Ingen forfader fundet for merge diff"
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr "Flere merge forfædre fundet for merge sammenligning"
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Kan ikke sammenligne repositories uden en fælles forfader"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Intet svar"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Ukendt fejl"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Forespørgslen kunne ikke forstås af serveren på grund af fejlformet "
 "syntaks."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Uautoriseret adgang til ressource"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Du har ikke tilladelse til at se denne side"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Kunne ikke finde ressourcen"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -128,14 +128,14 @@
 "Serveren stødte på en uventet tilstand, som forhindrede den i at opfylde "
 "anmodningen."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s committed den %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -143,12 +143,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Changesettet var for stor og blev afskåret..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "Feed for %s %s"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Ændringer på repository %s"
@@ -168,78 +168,78 @@
 msgid "%s at %s"
 msgstr "%s fra %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Du kan kun slette filer, hvor revisionen er en gyldig branch"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Slettet fil %s via Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Successfuldt slettet filen %s"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Fejl opstået under commit"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr "Du kan kun redigere filer, hvor revisionen er en gyldig branch"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Redigeret fil %s via Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Ingen ændringer"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Successfuldt committed til %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Tilføjet fil via Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Intet indhold"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Intet filnavn"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Placeringen skal være en relativ sti og må ikke indeholde .. i stien"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Downloads er deaktiveret"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Ukendt revision %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Tomt repository"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Ukendt arkivtype"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
@@ -247,14 +247,14 @@
 msgid "Changesets"
 msgstr "Changesets"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 #, fuzzy
 msgid "Branches"
 msgstr "Branches"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Tags"
 
@@ -263,11 +263,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Der opstod en fejl under repository forking %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Grupper"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -280,7 +280,7 @@
 msgid "Repositories"
 msgstr "Repositories"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
@@ -288,164 +288,164 @@
 msgid "Branch"
 msgstr "Branch"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Lukkede Branches"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Tag"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Bogmærke"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Offentlig journal"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Journal"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Dårlig captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Du har succesfuldt registreret med %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "En bekræftelseskode til ændring af adgangskode er sendt"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Ugyldig token for ændring af adgangskode"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Successfuld ændring af adgangskode"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Ugyldig reviewer \"%s\" angivet"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (lukket)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 #, fuzzy
 msgid "Changeset"
 msgstr "Changeset"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Speciel"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Bogmærker"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Fejl ved oprettelse af pull-forespørgsel: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Der opstod en fejl under oprettelse af pull-forespørgsel"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Åbnede ny pull-forespørgsel med success"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr "Ny pull-forespørgsel iteration oprettet"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr "I mellemtiden er de følgende reviewers tilføjet: %s"
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr "I mellemtiden er de følgende reviewers fjernet: %s"
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Ingen beskrivelse"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Pull-forespørgsel opdateret"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Slettede pull-forespørgsel med success"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr "Revision %s er ikke fundet i %s"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 "Fejl: Changesets ikke fundet, ved visning af pull-forespørgsel fra %s."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Denne pull-forespørgsel er allerede merged til %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "Denne pull-forespørgsel er lukket og kan ikke opdateres."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr "Følgende yderligere ændringer er tilgængelige på %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 "Ingen yderligere changesets fundet ved iteration på denne pull-"
 "forespørgsel."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Bemærk: Branch %s har et andet head: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 msgid "Git pull requests don't support iterating yet."
 msgstr "Git pull-forespørgsler supportere ej iteration endnu."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
@@ -453,30 +453,30 @@
 "Fejl: Nogle changesets kunne ikke findes ved visning af pull-forespørgsel "
 "fra %s."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 "Diff'en kunne ikke vises - pull-forespørgslens revisions kunne ikke "
 "findes."
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Ugyldig søgning. Prøv at citere det."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Der opstod en fejl under søgning."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Ingen data er klar endnu"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Statistik er slået fra for dette repository"
@@ -489,80 +489,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "Der opstod en fejl under opdatering af auth-indstillinger"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Standard-indstillinger opdateret successfuldt"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Der opstod en fejl under opdatering af standarder"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "For evigt"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 minutter"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 time"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 dag"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 måned"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Levetid"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Der opstod en fejl under oprettelse af gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Slettet gist %s"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Uændret"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Opdateret gist-indhold successfuldt"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Opdateret gist-data successfuldt"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Der opstod en fejl under opdatering af gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Du kan ikke redigere denne bruger, da den er afgørende for hele "
@@ -573,7 +573,7 @@
 msgstr "Din konto er blevet opdateret successfuldt"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Der opstod en fejl under opdatering af bruger %s"
@@ -583,45 +583,45 @@
 msgstr "Der opstod en fejl under opdatering af bruger adgangskode"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Tilføjet email %s til bruger"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Der opstod en fejl under tilføjelse af email"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "Fjernet email fra brugeren"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API-nøgle oprettet successfuldt"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "API-nøgle nulstillet successfuldt"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API-nøgle slettet successfuldt"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "API-nøgle oprettet successfuldt"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -699,11 +699,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Tilladt med automatisk kontoaktivering"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Manuel aktivering af ekstern konto"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Automatisk aktivering af ekstern konto"
 
@@ -725,59 +725,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Der opstod en fejl under opdatering af tilladelser"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Der opstod en fejl under oprettelse af repository-gruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Oprettet repository-gruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Opdateret repository-gruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Der opstod en fejl under opdatering af repository-gruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Denne gruppe indeholder %s repositories og kan ikke slettes"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Denne gruppe indeholder %s undergrupper og kan ikke slettes"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Fjernet repository-gruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Der opstod en fejl under sletning af repository-gruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Kan ikke tilbagekalde tilladelse for én selv som admin"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Repository-gruppe tilladelser opdateret"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Der opstod en fejl under tilbagekaldelse af tilladelse"
 
@@ -906,7 +906,7 @@
 msgid "Updated VCS settings"
 msgstr "Opdateret VCS-indstillinger"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -975,96 +975,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh reindex-opgave skeduleret"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Oprettet brugergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Der opstod en fejl under oprettelse af brugergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Opdateret brugergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Der opstod en fejl under opdatering af brugergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Brugergruppe slettet succesfuldt"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Der opstod en fejl under sletning af brugergruppe"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Brugergrupper-tilladelser opdateret"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Tilladelser opdateret"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Der opstod en fejl under gemning af tilladelser"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Bruger %s oprettet"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Der opstod en fejl under oprettelse af bruger %s"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Bruger opdateret"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Slettet bruger"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Der opstod en fejl under sletning af bruger %s"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "Standardbrugeren kan ikke redigeres"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Tilføjet IP-adresse %s til bruger-whitelist"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Der opstod en fejl under tilføjelse af IP-adresse"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "Fjernet IP-adresse fra bruger-whitelist"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr "Du skal være registreret bruger for at kunne udføre denne handling"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Du skal være logget ind for at se denne side"
 
@@ -1097,170 +1097,170 @@
 "Changeset var for stor, og blev afskåret, brug diff menu for at få vist "
 "denne diff"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Ingen ændringer fundet"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Slettet branch: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Oprettet tag: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Changeset %s ikke fundet"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Vis alle kombineret changesets %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Sammenlign visning"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "og"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s flere"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "revisioner"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Fork-navn %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull-forespørgsel %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[slettet] repository"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[oprettet] repository"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[oprettet] repository som fork"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[forked] repository"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[opdateret] repository"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[hentet] arkiv fra repository"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[slettet] repository"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[oprettet] bruger"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[opdateret] bruger"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[oprettet] brugergruppe"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[opdateret] brugergruppe"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[kommenterede] på revision i repository"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[kommenterede] på pull-forespørgsel for"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[lukket] pull-forespørgsel for"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[pushed] ind i"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[committed via kallithea] ind i repository"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[pulled fra remote] ind i repository"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[pulled] fra"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[begyndt at følge] repository"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[stoppet at følge] repository"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " og %s flere"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Ingen filer"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "ny fil"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "del"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "omdøb"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1270,96 +1270,98 @@
 "%s repository er ikke knyttet til db, måske var det skabt eller omdøbt "
 "fra filsystemet, kør applikationen igen for at scanne repositories"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "i %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s siden"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "i %s og %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s og %s siden"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "lige nu"
 
@@ -1368,136 +1370,136 @@
 msgid "on line %s"
 msgstr "på linje %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Omtale]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "top-niveau"
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr "Kallithea Administrator"
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr "Standard-bruger har ikke adgang til nye repositories"
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
-msgstr "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr "Standard-bruger har læse-adgang til nye repositories"
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
+msgstr "Standard-bruger har skrive-adgang til nye repositories"
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr "Standard-bruger har ikke adgang til nye repositories"
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
-msgstr "Standard-bruger har læse-adgang til nye repositories"
+msgid "Default user has admin access to new repositories"
+msgstr "Standard-bruger har admin-adgang til nye repositories"
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
-msgstr "Standard-bruger har skrive-adgang til nye repositories"
-
-#: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
-msgstr "Standard-bruger har admin-adgang til nye repositories"
-
-#: kallithea/model/db.py:1644
 msgid "Default user has no access to new repository groups"
 msgstr "Standard-bruger har ikke adgang til nye repository-grupper"
 
-#: kallithea/model/db.py:1645
+#: kallithea/model/db.py:1642
 msgid "Default user has read access to new repository groups"
 msgstr "Standard-bruger har læse-adgang til nye repository-grupper"
 
-#: kallithea/model/db.py:1646
+#: kallithea/model/db.py:1643
 msgid "Default user has write access to new repository groups"
 msgstr "Standard-bruger har skrive-adgang til nye repository-grupper"
 
-#: kallithea/model/db.py:1647
+#: kallithea/model/db.py:1644
 msgid "Default user has admin access to new repository groups"
 msgstr "Standard-bruger har admin-adgang til nye repository-grupper"
 
-#: kallithea/model/db.py:1649
+#: kallithea/model/db.py:1646
 msgid "Default user has no access to new user groups"
 msgstr "Standard-bruger har ikke adgang til nye brugergrupper"
 
-#: kallithea/model/db.py:1650
+#: kallithea/model/db.py:1647
 msgid "Default user has read access to new user groups"
 msgstr "Standard-bruger har læse-adgang til nye brugergrupper"
 
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
+msgstr "Standard-bruger har skrive-adgang til nye brugergrupper"
+
+#: kallithea/model/db.py:1649
+msgid "Default user has admin access to new user groups"
+msgstr "Standard-bruger har admin-adgang til nye brugergrupper"
+
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr "Standard-bruger har skrive-adgang til nye brugergrupper"
+msgid "Only admins can create repository groups"
+msgstr "Kun administratorer kan oprette repository-grupper"
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
-msgstr "Standard-bruger har admin-adgang til nye brugergrupper"
+msgid "Non-admins can create repository groups"
+msgstr "Ikke-administratorer kan oprette repository-grupper"
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
-msgstr "Kun administratorer kan oprette repository-grupper"
+msgid "Only admins can create user groups"
+msgstr "Kun administratorer kan oprette brugergrupper"
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
-msgstr "Ikke-administratorer kan oprette repository-grupper"
-
-#: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
-msgstr "Kun administratorer kan oprette brugergrupper"
-
-#: kallithea/model/db.py:1658
 msgid "Non-admins can create user groups"
 msgstr "Ikke-administratorer kan oprette brugergrupper"
 
-#: kallithea/model/db.py:1660
+#: kallithea/model/db.py:1657
 msgid "Only admins can create top level repositories"
 msgstr "Kun administratorer kan oprette top-niveau repositories"
 
-#: kallithea/model/db.py:1661
+#: kallithea/model/db.py:1658
 msgid "Non-admins can create top level repositories"
 msgstr "Ikke-administratorer kan oprette top-niveau repositories"
 
-#: kallithea/model/db.py:1663
+#: kallithea/model/db.py:1660
 msgid ""
 "Repository creation enabled with write permission to a repository group"
 msgstr ""
 "Repository oprettelse aktiveret med skriveadgang til en repository-gruppe"
 
-#: kallithea/model/db.py:1664
+#: kallithea/model/db.py:1661
 msgid ""
 "Repository creation disabled with write permission to a repository group"
 msgstr ""
 "Repository oprettelse deaktiveret med skriveadgang til en repository-"
 "gruppe"
 
-#: kallithea/model/db.py:1666
+#: kallithea/model/db.py:1663
 msgid "Only admins can fork repositories"
 msgstr "Kun admins kan fork repositories"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 msgid "Non-admins can fork repositories"
 msgstr "Ikke-administratorer kan forke repositories"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "Registrering deaktiveret"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr "Brugerregistrering med manuel kontoaktivering"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr "Brugerregistrering med automatisk kontoaktivering"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "Ikke gennemgået"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "Under gennemgang"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 msgid "Not approved"
 msgstr "Ikke godkendt"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Godkendt"
 
@@ -1523,7 +1525,7 @@
 msgid "Name must not contain only digits"
 msgstr "Navn må ikke kun indeholde cifre"
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
@@ -1532,12 +1534,12 @@
 "[Kommentar] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" på "
 "%(branch)s"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Ny bruger %(new_username)s registreret"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
@@ -1546,7 +1548,7 @@
 "[Gennemgang] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" fra "
 "%(pr_source_branch)s af %(pr_owner_username)s"
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
@@ -1555,11 +1557,11 @@
 "[Kommentar] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" fra "
 "%(pr_source_branch)s af %(pr_owner_username)s"
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "Lukning"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
@@ -1567,110 +1569,110 @@
 "%(user)s vil have dig til at gennemgå pull-forespørgsel %(pr_nice_id)s: "
 "%(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr "Kan ikke oprette en tom pull-forespørgsel"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset %s not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Changeset %s ikke fundet"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2359,7 +2361,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3402,7 +3404,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3642,53 +3644,53 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4317,23 +4319,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4342,7 +4344,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4351,8 +4353,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5316,45 +5318,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-05-29 22:52+0000\n"
 "Last-Translator: ssantos <ssantos@web.de>\n"
 "Language-Team: German <https://hosted.weblate.org/projects/kallithea/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.7-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Es gibt noch keine Änderungssätze"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,38 +33,38 @@
 msgid "None"
 msgstr "Keine"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(geschlossen)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Zeige unsichtbare Zeichen"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignoriere unsichtbare Zeichen"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Erhöhe diff-Kontext auf %(num)s Zeilen"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "Keine Berechtigung zum Ändern des Status des Pull Requests"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Pull-Request %s erfolgreich gelöscht"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Die angegebene Version existiert nicht in diesem Repository"
 
@@ -82,52 +82,52 @@
 "Ohne einen gemeinsamen Vorfahren ist ein Vergleich der Repositories nicht "
 "möglich"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "Kann leeren diff nicht anzeigen"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr "Es konnte kein Vorfahre für den merge diff gefunden werden"
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr "Es wurden mehrere merge Vorfahren für den merge Vergleich gefunden"
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 "Ohne einen gemeinsamen Vorfahren ist ein Vergleich der Repositories nicht "
 "möglich"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Keine Rückmeldung"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Unbekannter Fehler"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Die Anfrage konnte wegen ungültiger Syntax vom Server nicht ausgewertet "
 "werden."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Unauthorisierter Zugang zur Ressource"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Du hast keine Rechte, um diese Seite zu betrachten"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Die Ressource konnte nicht gefunden werden"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -135,14 +135,14 @@
 "Aufgrund einer unerwarteten Gegebenheit konnte der Server diese Anfrage "
 "nicht vollenden."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s committed am %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -150,12 +150,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Der Änderungssatz war zu groß und wurde abgeschnitten..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s Feed"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Änderungen im %s Repository"
@@ -175,94 +175,94 @@
 msgid "%s at %s"
 msgstr "%s auf %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Dateien können nur gelöscht werden, deren Revision ein gültiger Branch ist"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Datei %s via Kallithea gelöscht"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Datei %s erfolgreich gelöscht"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Während des Commits trat ein Fehler auf"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 #, fuzzy
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Dateien können nur editiert werden, deren Revision ein gültiger Branch ist"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Datei %s via Kallithea editiert"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Keine Änderungen"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Der Commit zu %s war erfolgreich"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Datei via Kallithea hinzugefügt"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Kein Inhalt"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Kein Dateiname"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "Der Ort muss ein relativer Pfad sein und darf nicht .. enthalten"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Downloads gesperrt"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Unbekannte Revision %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Leeres Repository"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Unbekannter Archivtyp"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Änderungssätze"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Entwicklungszweige"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Tags"
 
@@ -271,11 +271,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Während des Forkens des Repositorys trat ein Fehler auf: %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Gruppen"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -287,208 +287,208 @@
 msgid "Repositories"
 msgstr "Repositories"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Zweig"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Geschlossene Branches"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Marke"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Lesezeichen"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Öffentliches Logbuch"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Logbuch"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Falsches Captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Sie haben sich erfolgreich bei %s registriert"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Ihr Link um das Passwort zurückzusetzen wurde versendet"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Ungültiges Token zum Zurücksetzen des Passworts."
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Erfolgreich Kennwort geändert"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Ungültigen Begutachter \"%s\" angegeben"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (geschlossen)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Änderungssatz"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Spezial"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Branches anderer"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Lesezeichen"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Fehler beim Erstellen des Pull-Requests: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Während des Erstellens des Pull Requests trat ein Fehler auf"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Es wurde erfolgreich ein neuer Pullrequest eröffnet"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "Pull Request Update erstellt"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr "Es wurden inzwischen folgende Begutachter hinzugefügt: %s"
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr "Es wurden inzwischen folgende Begutachter entfernt: %s"
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Keine Beschreibung"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Pull Request aktualisiert"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Erfolgreich Pull-Request gelöscht"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr "Die Revision %s konnte in %s nicht gefunden werden"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Dieser Pull Request wurde bereits in %s integriert."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 "Dieser Pull Request wurde geschlossen und kann daher nicht aktualisiert "
 "werden."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, fuzzy, python-format
 #| msgid "The following changes are available on %s:"
 msgid "The following additional changes are available on %s:"
 msgstr "Die folgenden Änderungen sind verfügbar unter %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 #, fuzzy
 #| msgid "No changesets found for updating this pull request."
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Hinweis: Branch %s hat einen anderen Head: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 #, fuzzy
 #| msgid "Git pull requests don't support updates yet."
 msgid "Git pull requests don't support iterating yet."
 msgstr "Git Pull Request unterstützen bisher keine Updates."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 "Der diff kann nicht angezeigt werden. Die Pull Request Revisionen konnten "
 "nicht gefunden werden."
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 "Ungültige Suchanfrage. Versuchen sie es in Anführungzeichen zu setzen."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr "Der Server hat keinen Suchindex."
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Während der Suchoperation trat ein Fehler auf."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Es stehen noch keine Daten zur Verfügung"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Statistiken sind deaktiviert für dieses Repository"
@@ -501,80 +501,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "Fehler bei der Änderung der Anmeldeeinstellungen aufgetreten"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Standardeinstellungen erfolgreich geupdated"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Ein Fehler trat beim updaten der Standardeinstellungen auf"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Immer"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 Minuten"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 Stunde"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 Tag"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 Monat"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Lebenszeit"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Ein fehler trat auf bei der Erstellung des gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "gist %s gelöscht"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Ungeändert"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Erfolgreich Kerninhalt aktualisiert"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Erfolgreich Kerndaten aktualisiert"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Fehler beim Aktualisieren der Kerndaten %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Sie können diesen Benutzer nicht editieren, da er von entscheidender "
@@ -585,7 +585,7 @@
 msgstr "Ihr Account wurde erfolgreich aktualisiert"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Fehler beim Aktualisieren der Benutzer %s"
@@ -595,45 +595,45 @@
 msgstr "Fehler bei der Änderung des Kennworts"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Die EMail Addresse %s wurde zum Benutzer hinzugefügt"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Währen der Speicherung der EMail Addresse trat ein Fehler auf"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "Die EMail Addresse wurde vom Benutzer entfernt"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API Key wurde erfolgreich erstellt"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "API-Schlüssel erfolgreich zurückgesetzt"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API-Schlüssel erfolgreich gelöscht"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "API Key wurde erfolgreich erstellt"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -711,11 +711,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Erlaubt mit automatischer Kontoaktivierung"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Manuelle Aktivierung externen Kontos"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Automatische Aktivierung externen Kontos"
 
@@ -737,59 +737,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Fehler bei der Änderung der globalen Berechtigungen"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Fehler bei der Erstellung der Repositoriumsgruppe %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Repositoriumsgruppe %s erstellt"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Repositoriumsgruppe %s aktualisiert"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Fehler bei der Aktualisierung der Repositoriumsgruppe %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Die Gruppe enthält %s Repositorys und kann nicht gelöscht werden"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Diese Gruppe enthält %s Untergruppen und kann nicht gelöscht werden"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Repositoriumsgruppe %s entfernt"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Fehler beim Löschen der Repositoriumsgruppe %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Als Administrator kann man sich keine Berechtigungen entziehen"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Berechtigungen der Repositoriumsgruppe aktualisiert"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Fehler beim Entzug der Berechtigungen"
 
@@ -920,7 +920,7 @@
 msgid "Updated VCS settings"
 msgstr "VCS-Einstellungen aktualisiert"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -998,100 +998,100 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh Reindizierungs Aufgabe wurde zur Ausführung geplant"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Nutzergruppe %s erstellt"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 "Es ist ein Fehler während der Erstellung der Nutzergruppe %s aufgetreten"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Aktualisierte Nutzergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 "Während des Updates der Benutzergruppe %s ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Die Nutzergruppe wurde erfolgreich entfernt"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Während des Löschens der Benutzergruppe ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "Zielgruppe kann nicht die gleiche Gruppe sein"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Berechtigungen der Benutzergruppe wurden aktualisiert"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Berechtigungen wurden aktualisiert"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 "Es ist ein Fehler während des Speicherns der Berechtigungen aufgetreten"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Nutzer %s erstellt"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Während des Erstellens des Benutzers %s ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Der Benutzer wurde erfolgreich aktualisiert"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Der Nutzer wurde erfolgreich gelöscht"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Während der Löschen des Benutzers trat ein Fehler auf"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "Der Standard-Benutzer kann nicht bearbeitet werden"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Die IP-Adresse %s wurde zur Nutzerwhitelist hinzugefügt"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Während des Speicherns der IP-Adresse ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "IP-Adresse wurde von der Nutzerwhitelist entfernt"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 "Sie müssen ein Registrierter Nutzer sein um diese Aktion durchzuführen"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Sie müssen sich anmelden um diese Seite aufzurufen"
 
@@ -1128,171 +1128,171 @@
 "Der Änderungssatz war zu groß und wurde abgeschnitten, benutzen sie das "
 "Diff Menü um die Unterschiede anzuzeigen"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Keine Änderungen erkannt"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Branch %s gelöscht"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Tag %s erstellt"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "Änderungssatz nicht gefunden"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Zeige alle Kombinierten Änderungensätze %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Vergleichsansicht"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "und"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s mehr"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "revisionen"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Fork Name %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull Request %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[gelöscht] Repository"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[erstellt] Repository"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[erstellt] Repository als Fork"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[forked] Repository"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[aktualisiert] Repository"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "Archiv von Repository [heruntergeladen]"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "Repository [gelöscht]"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "Benutzer [erstellt]"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "Benutzer [akutalisiert]"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "Benutzergruppe [erstellt]"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "Benutzergruppe [aktualisiert]"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "Revision [kommentiert] in Repository"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "Pull Request [kommentiert] für"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "Pull Request [geschlossen] für"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[Pushed] in"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[via Kallithea] in Repository [committed]"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[Pulled von Remote] in Repository"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[Pulled] von"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[Following gestartet] für Repository"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[Following gestoppt] für Repository"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " und %s weitere"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Keine Dateien"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "neue Datei"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "entf"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "umbenennen"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1303,96 +1303,98 @@
 "es im Dateisystem erstellt oder umbenannt. Bitte starten sie die "
 "Applikation erneut um die Repositories neu zu Indizieren"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d Jahr"
 msgstr[1] "%d Jahre"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d Monat"
 msgstr[1] "%d Monate"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d Tag"
 msgstr[1] "%d Tage"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d Stunde"
 msgstr[1] "%d Stunden"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d Minute"
 msgstr[1] "%d Minuten"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d Sekunde"
 msgstr[1] "%d Sekunden"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "in %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "vor %s"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "in %s und %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s und %s her"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "jetzt gerade"
 
@@ -1401,140 +1403,140 @@
 msgid "on line %s"
 msgstr "in Zeile %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Mention]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "höchste Ebene"
 
-#: kallithea/model/db.py:1637
+#: kallithea/model/db.py:1634
 msgid "Kallithea Administrator"
 msgstr "Kallithea Administrator"
 
-#: kallithea/model/db.py:1639
+#: kallithea/model/db.py:1636
 msgid "Default user has no access to new repositories"
 msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repositories"
 
-#: kallithea/model/db.py:1640
+#: kallithea/model/db.py:1637
 msgid "Default user has read access to new repositories"
 msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repositories"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 msgid "Default user has write access to new repositories"
 msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Repositories"
 
-#: kallithea/model/db.py:1642
+#: kallithea/model/db.py:1639
 msgid "Default user has admin access to new repositories"
 msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Repositories"
 
-#: kallithea/model/db.py:1644
+#: kallithea/model/db.py:1641
 msgid "Default user has no access to new repository groups"
 msgstr ""
 "Der Standard-Benutzer hat keinen Zugriff auf neue Repository-Gruppen"
 
-#: kallithea/model/db.py:1645
+#: kallithea/model/db.py:1642
 msgid "Default user has read access to new repository groups"
 msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repository-Gruppen"
 
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
+msgstr "Der Standard-Benutzer Schreibrechte auf neuen Repository-Gruppen"
+
+#: kallithea/model/db.py:1644
+msgid "Default user has admin access to new repository groups"
+msgstr "Der Standard-Benutzer Admin-Rechte auf neuen Repository-Gruppen"
+
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
-msgstr "Der Standard-Benutzer Schreibrechte auf neuen Repository-Gruppen"
+msgid "Default user has no access to new user groups"
+msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Benutzer-Gruppen"
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
-msgstr "Der Standard-Benutzer Admin-Rechte auf neuen Repository-Gruppen"
+msgid "Default user has read access to new user groups"
+msgstr "Der Standard-Benutzer hat Leserechte auf neuen Benutzer-Gruppen"
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
+msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Benutzer-Gruppen"
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
-msgstr "Der Standard-Benutzer hat Leserechte auf neuen Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1652
 msgid "Default user has admin access to new user groups"
 msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Benutzer-Gruppen"
 
-#: kallithea/model/db.py:1654
+#: kallithea/model/db.py:1651
 msgid "Only admins can create repository groups"
 msgstr "Nur Admins können Repository-Gruppen erstellen"
 
+#: kallithea/model/db.py:1652
+msgid "Non-admins can create repository groups"
+msgstr "Nicht-Admins können Repository-Gruppen erstellen"
+
+#: kallithea/model/db.py:1654
+msgid "Only admins can create user groups"
+msgstr "Nur Admins können Benutzer-Gruppen erstellen"
+
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
-msgstr "Nicht-Admins können Repository-Gruppen erstellen"
+msgid "Non-admins can create user groups"
+msgstr "Nicht-Admins können Benutzer-Gruppen erstellen"
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
-msgstr "Nur Admins können Benutzer-Gruppen erstellen"
+msgid "Only admins can create top level repositories"
+msgstr "Nur Admins können Repositories auf oberster Ebene erstellen"
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
-msgstr "Nicht-Admins können Benutzer-Gruppen erstellen"
+msgid "Non-admins can create top level repositories"
+msgstr "Nicht-Admins können Repositories oberster Ebene erstellen"
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
-msgstr "Nur Admins können Repositories auf oberster Ebene erstellen"
-
-#: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
-msgstr "Nicht-Admins können Repositories oberster Ebene erstellen"
-
-#: kallithea/model/db.py:1663
 msgid ""
 "Repository creation enabled with write permission to a repository group"
 msgstr ""
 "Erstellung von Repositories mit Schreibzugriff für Repositorygruppe "
 "aktiviert"
 
-#: kallithea/model/db.py:1664
+#: kallithea/model/db.py:1661
 msgid ""
 "Repository creation disabled with write permission to a repository group"
 msgstr ""
 "Erstellung von Repositories mit Schreibzugriff für Repositorygruppe "
 "deaktiviert"
 
-#: kallithea/model/db.py:1666
+#: kallithea/model/db.py:1663
 msgid "Only admins can fork repositories"
 msgstr "Nur Admins können Repositories forken"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 msgid "Non-admins can fork repositories"
 msgstr "Nicht-Admins können Repositorys forken"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "Registrierung deaktiviert"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr "Benutzerregistrierung mit manueller Kontoaktivierung"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr "Benutzerregistrierung mit automatischer Kontoaktivierung"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "Nicht Begutachtet"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "In Begutachtung"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 #, fuzzy
 #| msgid "Approved"
 msgid "Not approved"
 msgstr "Akzeptiert"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Akzeptiert"
 
@@ -1560,7 +1562,7 @@
 msgid "Name must not contain only digits"
 msgstr "Name darf nicht nur Ziffern enthalten"
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, fuzzy, python-format
 #| msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
 msgid ""
@@ -1570,43 +1572,43 @@
 "Kommentar für %(repo_name)s Changeset %(short_id)s in %(branch)s erstellt "
 "von %(comment_username)s"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Neuer Benutzer %(new_username)s registriert"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "Schließen"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, fuzzy, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 "%(user)s möchte ein Review des Pull Request #%(pr_id)s: %(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Error creating pull request: %s"
 msgid "Cannot create empty pull request"
 msgstr "Fehler beim Erstellen des Pull-Requests: %s"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
@@ -1615,29 +1617,29 @@
 "Pull-Request kann nicht erstellt werden - Criss Cross Merge erkannt, "
 "bitte eine spätere %s-Revision in %s zusammenführen."
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr "Sie sind nicht berechtigt, den Pull-Request anzulegen."
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 #, fuzzy
 #| msgid "Missing changesets since the previous pull request:"
 msgid "Missing changesets since the previous iteration:"
 msgstr "Fehlende Changesets seit letztem Pull Request:"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, fuzzy, python-format
 #| msgid "New changesets on %s %s since the previous pull request:"
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "Neue Changesets in %s %s seit dem letzten Pull Request:"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 #, fuzzy
 #| msgid "Ancestor didn't change - show diff since previous version:"
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr "Vorgänger unverändert - zeige Diff zu lezter Version:"
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, fuzzy, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
@@ -1646,42 +1648,42 @@
 "Dieser Pull Request basiert auf einer anderen %s Revision. Daher ist kein "
 "Simple Diff verfügbar."
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, fuzzy, python-format
 #| msgid "No changes found on %s %s since previous version."
 msgid "No changes found on %s %s since previous iteration."
 msgstr "Keine Änderungen seit der letzten Version gefunden in %s %s."
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr "Geschlossen, nächste Iteration: %s ."
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "Letzter Tip"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Änderungssatz nicht gefunden"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "Neue Benutzerregistrierung"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 #, fuzzy
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
@@ -1689,7 +1691,7 @@
 "Sie können diesen Benutzer nicht löschen, da er von entscheidender "
 "Bedeutung für die gesamte Applikation ist"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1699,7 +1701,7 @@
 "nicht entfernt werden. Entweder muss der Besitzer geändert oder das "
 "Repository entfernt werden: %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1709,7 +1711,7 @@
 "kann daher nicht entfernt werden. Entweder muss der Besitzer geändert "
 "oder die Repositorygruppen müssen entfernt werden: %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1719,16 +1721,16 @@
 "nicht entfernt werden. Entweder muss der Besitzer geändert oder die "
 "Benutzergruppen müssen gelöscht werden: %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "Link zum Zurücksetzen des Passworts"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 #, fuzzy
 msgid "Password reset notification"
 msgstr "Link zum Zurücksetzen des Passworts"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2464,7 +2466,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3595,7 +3597,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "Einstellungen speichern"
 
@@ -3914,13 +3916,13 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repository Size"
 msgid "Repository page size"
 msgstr "Repository Größe"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
@@ -3928,11 +3930,11 @@
 "Anzahl der Elemente, die auf den Repository-Seiten angezeigt werden, "
 "bevor der Seitenumbruch angezeigt wird."
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr "Größe der Admin-Seite"
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
@@ -3940,29 +3942,29 @@
 "Anzahl der Elemente, die in den Gittern der Admin-Seiten angezeigt "
 "werden, bevor der Seitenumbruch angezeigt wird."
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Icons"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Öffentliches Repository-Symbol in Repositories anzeigen"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Privates Repository-Symbol in Repositories anzeigen"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 "Zeigt öffentliche/private Symbole neben den Namen der Repositories an."
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Einstellungen"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
@@ -3970,7 +3972,7 @@
 "Analysiert Meta-Tags aus dem Beschreibungsfeld des Repositorys und "
 "verwandelt sie in farbige Tags."
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr "Erkannte Meta-Tags stilisieren:"
 
@@ -4610,25 +4612,25 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 #, fuzzy
 msgid "Replaced by:"
 msgstr "Erstellt von"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 #, fuzzy
 msgid "Preceded by:"
 msgstr "Erstellt von"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4637,7 +4639,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4646,8 +4648,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5655,45 +5657,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "Dateien"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Mehr anzeigen"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "Commits"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "Dateien hinzugefügt"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "Dateien geändert"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "Dateien entfernt"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "Dateien entfernt"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "Commit"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "Datei hinzugefügt"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "Datei geändert"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "Datei entfernt"
 
--- a/kallithea/i18n/el/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/el/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-06-26 19:00+0000\n"
 "Last-Translator: THANOS SIOURDAKIS <siourdakisthanos@gmail.com>\n"
 "Language-Team: Greek <https://hosted.weblate.org/projects/kallithea/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.7.1-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Δεν υπάρχουν σετ αλλαγών ακόμα"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,38 +33,38 @@
 msgid "None"
 msgstr "Χωρίς"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(κλειστό)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Εμφάνιση κενού"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Αγνόηση κενού"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Αύξηση του diff πλαισίου σε %(num)s γραμμές"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "Χωρίς δικαιώματα αλλαγής της κατάστασης του αιτήματος έλξης"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Επιτυχής διαγραφή αιτήματος έλξης %s"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Δεν υπάρχει τέτοια αναθεώρηση για αυτό το αποθετήριο"
 
@@ -82,52 +82,52 @@
 "Δεν μπορεί να γίνει σύγκριση αποθετηρίων χωρίς να χρησιμοποιηθεί κοινός "
 "πρόγονος"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 "Δεν μπορεί να γίνει σύγκριση αποθετηρίων χωρίς να χρησιμοποιηθεί κοινός "
 "πρόγονος"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Χωρίς απόκριση"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Άγνωστο λάθος"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Η αίτηση δεν  μπόρεσε να ερμηνευτεί από τον εξυπηρετητή λόγω κακής "
 "διατύπωσης."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Ανεξουσιοδοτημένη πρόσβαση στον πόρο"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Δεν έχετε άδεια για να εμφανίσετε αυτή τη σελίδα"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Ο πόρος δεν μπορεί να βρεθεί"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -135,14 +135,14 @@
 "Ο εξυπηρετητής συνάντησε μια απρόσμενη κατάσταση που τον απέτρεψαν να "
 "πραγματοποιήσει την αίτηση."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s συνέβαλε στο %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -150,12 +150,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Το σετ αλλαγών ήταν πολύ μεγάλο και περικόπηκε..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s τροφοδοσία"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Αλλαγές στο αποθετήριο %s"
@@ -175,97 +175,97 @@
 msgid "%s at %s"
 msgstr "%s την %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Μπορείτε να διαγράψετε μόνο αρχεία με αναθεώρηση που βρίσκονται σε έγκυρη "
 "διακλάδωση"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Διαγραφή αρχείου %s μέσω του Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Επιτυχής διαγραφή αρχείου %s"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Συνέβη λάθος κατά το commit"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Μπορείτε να επεξεργαστείτε μόνο αρχεία σε αναθεώρηση που βρίσκονται σε "
 "έγκυρη διακλάδωση"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Επεξεργασία αρχείου %s μέσω του Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Καμία αλλαγή"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Επιτυχής παράδοση σε %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Προσθήκη αρχείου μέσω Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Χωρίς περιεχόμενο"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Χωρίς όνομα αρχείου"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Η τοποθεσία πρέπει να είναι σχετική διαδρομή και να μην περιέχει .. μέσα "
 "της"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Οι μεταφορτώσεις απενεργοποιήθηκαν"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Άγνωστη αναθεώρηση %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Άδειο αποθετήριο"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Άγνωστος τύπος αρχειοθέτησης"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Σετ αλλαγών"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Κλάδοι"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Ετικέτες"
 
@@ -274,11 +274,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Συνέβει ένα λάθος κατά την διακλάδωση του αποθετηρίου %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Ομάδες"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -290,203 +290,203 @@
 msgid "Repositories"
 msgstr "Αποθετήρια"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Κλάδος"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Κλειστοί Κλάδοι"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Ετικέτα"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Σελιδοδείκτης"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Δημόσιο Ημερολόγιο"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Ημερολόγιο"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Λάθος captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Εγγραφήκατε επιτυχώς στο %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Στάλθηκε ένας κωδικός επιβεβαίωσης επαναφοράς του συνθηματικού"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Άκυρο τεκμήριο (token) επαναφοράς του συνθηματικού"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Το συνθηματικό ενημερώθηκε επιτυχώς"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Καθορίστηκε άκυρος σχολιαστής \"%s\""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (κλειστό)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Σετ αλλαγών"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Ειδικός"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Ομότιμοι κλάδοι"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Σελιδοδείκτες"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Λάθος κατά τη δημιουργία αιτήματος έλξης (pull request)"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Ένα νέο αίτημα έλξης (pull request) δημιουργήθηκε επιτυχώς"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "Δημιουργήθηκε ενημέρωση αιτήματος έλξης"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Χωρίς περιγραφή"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Ενημερώθηκε η αίτηση έλξης"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Επιτυχής διαγραφή αιτήματος έλξης"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Το αίτημα έλξης έχει ήδη συγχωνευτεί με το %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "Αυτό το αίτημα έλξης έχει κλείσει και δεν μπορεί να ενημερωθεί."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, fuzzy, python-format
 #| msgid "The following changes are available on %s:"
 msgid "The following additional changes are available on %s:"
 msgstr "Οι ακόλουθες αλλαγές είναι διαθέσιμες στο %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 #, fuzzy
 #| msgid "No changesets found for updating this pull request."
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Σημείωση: Ο κλάδος %s έχει άλλη κεφαλή (head): %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 #, fuzzy
 #| msgid "Git pull requests don't support updates yet."
 msgid "Git pull requests don't support iterating yet."
 msgstr "Αιτήματα έλξης του git δεν υποστηρίζουν ακόμα ενημερώσεις."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Άκυρο αίτημα αναζήτησης. Δοκιμάστε με εισαγωγικά."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Ένα λάθος συνέβη κατά την διαδικασία αναζήτησης."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Δεν υπάρχουν ακόμα έτοιμα δεδομένα"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Τα στατιστικά είναι απενεργοποιημένα για αυτό το αποθετήριο"
@@ -499,80 +499,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "παρουσιάστηκε βλάβη κατά την ενημέρωση των ρυθμίσεων εξουσιοδότησης"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Οι προεπιλεγμένες ρυθμίσεις ενημερώθηκαν επιτυχώς"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Συνέβη μία βλάβη κατά την ενημέρωση των προεπιλογών"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Πάντα"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 λεπτά"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 ώρα"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 ημέρα"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 μήνας"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Διάρκεια ζωής"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Συνέβη μία βλάβη κατά τη δημιουργία του gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Διαγράφηκε το gist %s"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Mη τροποποιημένo"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Το περιεχόμενο του gist ενημερώθηκε επιτυχώς"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Τα δεδομένα του gist ενημερώθηκαν επιτυχώς"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Σφάλμα συνέβη κατά την ενημέρωση του gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Δεν μπορείτε να επεξεργαστείτε αυτόν το χρήστη καθώς είναι κρίσιμος για "
@@ -583,7 +583,7 @@
 msgstr "Ο λογαριασμός σας ενημερώθηκε επιτυχώς"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Συνέβη ένα σφάλμα κατά την ενημέρωση του χρήστη %s"
@@ -593,45 +593,45 @@
 msgstr "Συνέβη ένα σφάλμα κατά την ενημέρωση του κωδικού του χρήστη"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Προστέθηκε το email %s στον χρήστη"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Συνέβη ένα σφάλμα κατά την αποθήκευση του email"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "Αφαιρέθηκε το email από τον χρήστη"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "Το API κλειδί δημιουργήθηκε επιτυχώς"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "Το API κλειδί επαναφέρθηκε επιτυχώς"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "Το API κλειδί διαγράφηκε επιτυχώς"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "Το API κλειδί δημιουργήθηκε επιτυχώς"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -709,11 +709,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Επιτρέπεται με αυτόματη ενεργοποίηση του λογαριασμού"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Χειροποίητη ενεργοποίηση εξωτερικού λογαριασμού"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Αυτόματη ενεργοποίηση εξωτερικού λογαριασμού"
 
@@ -735,59 +735,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Συνέβει μια βλάβη κατά την ενημέρωση των δικαιωμάτων"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Συνέβει μια βλάβη κατά την δημιουργία της ομάδας αποθετηρίου %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Δημιουργήθηκε η ομάδα αποθετηρίου %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Ενημερώθηκε η ομάδα αποθετηρίου %s"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Συνέβει μια βλάβη κατά την ενημέρωση της ομάδας αποθετηρίου %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Αυτή η ομάδα περιέχει %s αποθετήρια και δε μπορεί να διαγραφεί"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Αυτή η ομάδα περιέχει %s υποομάδες και δε μπορεί να διαγραφεί"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Αφαιρέθηκε η ομάδα αποθετηρίου %s"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Συνέβει μια βλάβη κατά την διαγραφή της ομάδας αποθετηρίου %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Δεν μπορείτε να ανακαλέσετε την άδεια σας ως διαχειριστής"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Τα δικαιώματα της ομάδας αποθετηρίου ενημερώθηκαν"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Συνέβει μια βλάβη κατά την ανάκληση της άδειας"
 
@@ -916,7 +916,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -984,96 +984,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1104,170 +1104,170 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1275,96 +1275,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1373,133 +1375,133 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1525,79 +1527,79 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Error creating pull request: %s"
 msgid "Cannot create empty pull request"
 msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 #, fuzzy
 #| msgid "Missing changesets since the previous pull request:"
 msgid "Missing changesets since the previous iteration:"
 msgstr "Ελλιπή σετ αλλαγών από την προηγούμενη αίτηση έλξης:"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, fuzzy, python-format
 #| msgid "New changesets on %s %s since the previous pull request:"
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "Καινούρια σετ αλλαγών στα %s %s από την προηγούμενη αίτηση έλξης:"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 #, fuzzy
 #| msgid "Ancestor didn't change - show diff since previous version:"
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 "Το γονικό δεν άλλαξε - εμφάνισε τις διαφορές από την προηγούμενη έκδοση:"
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, fuzzy, python-format
 #| msgid ""
 #| "This pull request is based on another %s revision and there is no "
@@ -1609,75 +1611,75 @@
 "Αυτή η αίτηση έλξης είναι βασισμένη σε μία άλλη %s αναθεώρηση και δεν "
 "υπάρχει ένα απλό diff."
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, fuzzy, python-format
 #| msgid "No changes found on %s %s since previous version."
 msgid "No changes found on %s %s since previous iteration."
 msgstr "Δεν βρέθηκαν αλλαγές στο %s %s από την προηγούμενη έκδοση."
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2366,7 +2368,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3416,7 +3418,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3656,55 +3658,55 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repositories"
 msgid "Repository page size"
 msgstr "Αποθετήρια"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4333,23 +4335,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4358,7 +4360,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4367,8 +4369,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5349,45 +5351,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/es/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/es/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2018-04-18 11:43+0000\n"
 "Last-Translator: Jesús Sánchez <jsanchezfdz95@gmail.com>\n"
 "Language-Team: Spanish <https://hosted.weblate.org/projects/kallithea/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.0-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Aún no hay cambios"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,38 +33,38 @@
 msgid "None"
 msgstr "Ninguno"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(cerrado)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Mostrar espacios en blanco"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorar espacios en blanco"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Aumentar el contexto del diff a %(num)s lineas"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "No tene permiso para cambiar el estado de la petición pull"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Petición de pull %s eliminada correctamente"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "La revisión no existe en este repositorio"
 
@@ -77,50 +77,50 @@
 msgid "Cannot compare repositories of different types"
 msgstr "No se pueden comparar repositorios de diferentes tipos"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "No se puede mostrar diff vacio"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "No se pueden comparar repositorios sin usar un ancestro común"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "No hay respuesta"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Error desconocido"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "La petición no ha podido ser atendida por el servidor debido un error de "
 "sintaxis."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Acceso no autorizado al recurso"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "No tiene permiso para ver esta página"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "No se ha encontrado el recurso"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -128,14 +128,14 @@
 "La petición no se ha podido completar debido a que el servidor encontró "
 "un problema inesperado."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s anotó en %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -143,12 +143,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "El cambio era demasiado grande y se redució..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s%s canal"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, fuzzy, python-format
 msgid "Changes on %s repository"
 msgstr "Cambios en %s repositorio"
@@ -168,93 +168,93 @@
 msgid "%s at %s"
 msgstr "%s en %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Sólo puede borrar archivos si la revisión pertenece a una rama válida"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Archivo %s eliminado mediante Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "El archivo %s se eliminó correctamente"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Ocurrió un error al anotar los cambios"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Sólo puede editar archivos si la revisión pertenece a una rama válida"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Archivo %s editado mediante Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "No hay cambios"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Anotado correctamente a %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Archivo añadido mediante Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Sin contenido"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Sin nombre de archivo"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 #, fuzzy
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "La ruta debe ser relativa y no debe contener .. en la ruta"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Descargas deshabilitadas"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Revisión desconocida %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Repositorio vacío"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Tipo de archivo desconocido"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Cambios"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Ramas"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Etiquetas"
 
@@ -263,11 +263,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Ocurrió un error mientras se bifurcaba el repositorio %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Grupos"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -279,204 +279,204 @@
 msgid "Repositories"
 msgstr "Repositorios"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Rama"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Ramas cerradas"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Etiqueta"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Marcador"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Registro público"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Registro"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "CAPTCHA erróneo"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "El registro en %s se ha efectuado correctamente"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Se ha enviado una confirmación de restauración de contraseña"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Señal de restauración de contraseña inválida"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Contraseña actualizada correctamente"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "El validador \"%s\" no es correcto"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (cerrado)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Cambio"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Especial"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 #, fuzzy
 msgid "Peer branches"
 msgstr "Ramas de los pares"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Marcadores"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Error al crear la petición de pull: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Ocurrió un error al crear la petición de pull"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "La petición de pull se ha creado correctamente"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "Actualización de la petición pull creada"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "No hay descripción"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Petición pull actualizada"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Petición pull eliminada correctamente"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "No se encontraron cambios para actualizar la petición pull."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "La petición pull ya ha sido incluida a %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "La petición pull esta cerrada y no se puede actualizar."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, fuzzy, python-format
 #| msgid "The following changes are available on %s:"
 msgid "The following additional changes are available on %s:"
 msgstr "Los siguientes cambios están disponibles en %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 #, fuzzy
 #| msgid "No changesets found for updating this pull request."
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "No se encontraron cambios para actualizar la petición pull."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, fuzzy, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Nota: la rama %s tiene otro head: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 #, fuzzy
 #| msgid "Git pull requests don't support updates yet."
 msgid "Git pull requests don't support iterating yet."
 msgstr "La peticiones pull de Git aún no soportan actualizaciones."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr "No se encontraron cambios para actualizar la petición pull."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Consulta de búsqueda inválida. Inténtelo entre comillas."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Ocurrió un error mientras se ejecutaba la búsqueda."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Todavía no hay datos disponibles"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Las estadísticas están deshabilitadas en este repositorio"
@@ -489,80 +489,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "ocurrió un error al actualizar los ajustes de autentificación"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Los ajustes predeterminados se han actualizado correctamente"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Ocurrió un error al actualizar los ajustes predeterminados"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Para siempre"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 minutos"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 hora"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 día"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 mes"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Tiempo de vida"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Ocurrió un error mientras se creaba el gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist %s eliminado"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Sin modificar"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Gist actualizado correctamente"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -571,7 +571,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -581,44 +581,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "Successfully deleted file %s"
 msgid "SSH key successfully deleted"
@@ -696,11 +696,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -722,59 +722,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -901,7 +901,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -968,96 +968,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1088,170 +1088,170 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1259,96 +1259,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1357,133 +1359,133 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1509,79 +1511,79 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Error creating pull request: %s"
 msgid "Cannot create empty pull request"
 msgstr "Error al crear la petición de pull: %s"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 #, fuzzy
 #| msgid "Missing changesets since the previous pull request:"
 msgid "Missing changesets since the previous iteration:"
 msgstr "Cambios que faltan desde la ultima petición de pull:"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, fuzzy, python-format
 #| msgid "New changesets on %s %s since the previous pull request:"
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "Cambios nuevos en %s %s desde la ultima petición pull:"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 #, fuzzy
 #| msgid "Ancestor didn't change - show diff since previous version:"
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 "El ascendente no ha cambiado - ver diferencias desde la versión anterior:"
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, fuzzy, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
@@ -1590,75 +1592,75 @@
 "La petición de pull está basada en otra %s revisión y no hay un diff "
 "simple."
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, fuzzy, python-format
 #| msgid "No changes found on %s %s since previous version."
 msgid "No changes found on %s %s since previous iteration."
 msgstr "No se encontró ningún cambio en %s %s desde la versión anterior."
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2347,7 +2349,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3394,7 +3396,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3634,55 +3636,55 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repositories"
 msgid "Repository page size"
 msgstr "Repositorios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4311,23 +4313,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4336,7 +4338,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4345,8 +4347,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5323,45 +5325,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/fr/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/fr/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-10-13 16:52+0000\n"
 "Last-Translator: Nathan <bonnemainsnathan@gmail.com>\n"
 "Language-Team: French <https://hosted.weblate.org/projects/kallithea/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.9-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Il n’y a aucun changement pour le moment"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,36 +33,36 @@
 msgid "None"
 msgstr "Aucun"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(fermé)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Afficher les espaces et tabulations"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorer les espaces et tabulations"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Augmenter le contexte du diff à %(num)s lignes"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr "Permission manquante pour changer le statut"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "La requête de pull %s a été supprimée avec succès"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Une telle révision n'existe pas pour ce dépôt"
 
@@ -75,50 +75,50 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Impossible de comparer des dépôts de types différents"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "Impossible d'afficher un diff vide"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr "Aucun ancêtre trouvé pour le diff de fusion"
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr "Plusieurs ancêtres de fusion trouvés pour la comparaison de fusion"
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Impossible de comparer des dépôts sans utiliser un ancêtre commun"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Pas de réponse"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Erreur inconnue"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Le serveur n’a pas pu interpréter la requête à cause d’une erreur de "
 "syntaxe."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Accès interdit à cette ressource"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Vous n’avez pas la permission de voir cette page"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Ressource introuvable"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -126,14 +126,14 @@
 "La requête n’a pu être traitée en raison d’une erreur survenue sur le "
 "serveur."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s a commité, le %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -141,12 +141,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Cet ensemble de changements était trop important et a été découpé…"
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "Flux %s de %s"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Changements sur le dépôt %s"
@@ -164,96 +164,96 @@
 msgid "%s at %s"
 msgstr "%s à %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Vous ne pouvez supprimer les fichiers que si la révision est une branche "
 "valide"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Le fichier %s a été supprimé via Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Suppression du fichier %s effectuée avec succès"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Une erreur est survenue durant le commit"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Vous ne pouvez modifier les fichiers que si la révision est une branche "
 "valide"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "%s édité via Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Aucun changement"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Commit réalisé avec succès sur %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "%s ajouté par Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Aucun contenu"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Aucun nom de fichier"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Le chemin doit être un chemin relatif et ne doit pas contenir .. dans le "
 "chemin"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Les téléchargements sont désactivés"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Révision %s inconnue"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Dépôt vide"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Type d’archive inconnu"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Changesets"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Branches"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Tags"
 
@@ -262,11 +262,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Une erreur est survenue durant le fork du dépôt %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Groupes"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -278,172 +278,172 @@
 msgid "Repositories"
 msgstr "Dépôts"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Branche"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Branches fermées"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Étiquette"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Signet"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Journal public"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Historique"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Mauvais captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Vous vous êtes inscrit avec succès avec %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 "Un lien de confirmation de réinitialisation de mot de passe a été envoyé"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Clé de réinitialisation de mot de passe invalide"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Mot de passe mis à jour avec succès"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Reviewer spécifié \"%s\" non valide"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (fermé)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Changements"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Spécial"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Branches appairées"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Signets"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Erreur de création de la demande de pull : %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Une erreur est survenue durant la création de la pull request"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "La requête de pull a été ouverte avec succès"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr "Nouvelle itération de requête de pull créée"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr "Entretemps, les relecteurs suivants on été ajoutés : %s"
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr "Entretemps, les relecteurs suivants ont été supprimés : %s"
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Aucune description"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Pull request mise à jour"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "La requête de pull a été supprimée avec succès"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr "Révision %s non trouvée dans %s"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 "Erreur : Pas de changeset trouvé lors de l'affichage la requête de pull "
 "de %s."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Cette pull request a déjà été fusionnée à %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "Cette pull request a été fermée et ne peut pas être mise à jour."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 "Les modifications additionnelles suivantes sont disponibles sur %s :"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "Pas de changeset additionnel trouvé pour cette requête de pull."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Note : La branche %s a une autre tête : %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 "Les itérations des requêtes de pull Git ne sont pas encore supportées."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
@@ -451,30 +451,30 @@
 "Erreur : certains changesets n'ont pas été trouvés lors de l'affichage la "
 "requête de pull depuis %s."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 "Le diff ne peut pas être affiché : révisions des requêtes de pull "
 "introuvables."
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Requête invalide. Essayez de la mettre entre guillemets."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr "Le serveur n'a pas d'index de recherche."
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Une erreur est survenue pendant la recherche."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Aucune donnée actuellement disponible"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "La mise à jour des statistiques est désactivée pour ce dépôt"
@@ -489,81 +489,81 @@
 "une erreur est survenue pendant la mise à jour des réglages "
 "d'authentification"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Mise à jour des réglages par défaut effectuée avec succès"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 "Une erreur est survenue durant la mise à jour des réglages par défaut"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Pour toujours"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 minute"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 heure"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 jour"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 mois"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Toujours"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Une erreur est survenue lors de la création du gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist %s supprimé"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Non modifié"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Le contenu du gist a été mis à jour avec succès"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Les données du gist on été mises à jour avec succès"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Une erreur est survenue durant la mise à jour du gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Vous ne pouvez pas éditer cet utilisateur ; il est nécessaire pour le bon "
@@ -574,7 +574,7 @@
 msgstr "Votre compte a été mis à jour avec succès"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Une erreur est survenue durant la mise à jour de l'utilisateur %s"
@@ -586,45 +586,45 @@
 "l'utilisateur"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "L’e-mail « %s » a été ajouté à l’utilisateur"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Une erreur est survenue durant l’enregistrement de l’e-mail"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "L’e-mail a été enlevé de l’utilisateur"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "Clé d'API créée avec succès"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "Clé d'API remise à zéro avec succès"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "Clé d'API supprimée avec succès"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "Clé d'API créée avec succès"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -702,11 +702,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Autorisé avec activation de compte automatique"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Activation manuelle du compte externe"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Activation automatique du compte externe"
 
@@ -728,61 +728,61 @@
 msgid "Error occurred during update of permissions"
 msgstr "Une erreur est survenue durant la mise à jour des permissions"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Une erreur est survenue durant la création du groupe de dépôts %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Groupe de dépôts %s créé"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Groupe de dépôts %s mis à jour"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 "Une erreur est survenue durant la mise à jour du groupe de dépôts %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Ce groupe contient %s dépôts et ne peut être supprimé"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Ce groupe contient %s sous-groupes et ne peut pas être supprimé"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Groupe de dépôts %s supprimé"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 "Une erreur est survenue durant la suppression du groupe de dépôts %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Impossible de révoquer votre permission d'administrateur"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Permissions du groupe de dépôts mises à jour"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Une erreur est survenue durant la révocation de la permission"
 
@@ -911,7 +911,7 @@
 msgid "Updated VCS settings"
 msgstr "Réglages des gestionnaires de versions mis à jour"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -984,100 +984,100 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "La tâche de réindexation Whoosh a été planifiée"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Groupe d'utilisateurs %s créé"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 "Une erreur est survenue durant la création du groupe d'utilisateurs %s"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Groupe d'utilisateurs %s mis à jour"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 "Une erreur est survenue durant la mise à jour du groupe d'utilisateurs %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Groupe d'utilisateurs supprimé avec succès"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 "Une erreur est survenue durant la suppression du groupe d'utilisateurs"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "Le groupe cible ne peut pas être le même"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Permissions du groupe d'utilisateurs mises à jour"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Permissions mises à jour"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Une erreur est survenue durant l’enregistrement des permissions"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Utilisateur %s créé"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Une erreur est survenue durant la création de l'utilisateur %s"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "L’utilisateur a été mis à jour avec succès"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Utilisateur supprimé avec succès"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Une erreur est survenue durant la suppression de l’utilisateur"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "L'utilisateur par défaut ne peut pas être modifié"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 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:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Une erreur est survenue durant la sauvegarde d'IP"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "L'adresse IP a été supprimée de la liste blanche"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 "Vous devez être un utilisateur enregistré pour effectuer cette action"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Vous devez être connecté pour visualiser cette page"
 
@@ -1114,170 +1114,170 @@
 "Cet ensemble de changements était trop gros pour être affiché et a été "
 "découpé, utilisez le menu « diff » pour afficher les différences"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Aucun changement détecté"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Branche supprimée : %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Étiquette créée : %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Ensemble de changements %s non trouvé"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Afficher les changements combinés %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Vue de comparaison"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "et"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s de plus"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "révisions"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Nom du fork %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Requête de pull %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[a supprimé] le dépôt"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[a créé] le dépôt"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[a créé] le dépôt en tant que fork"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[a forké] le dépôt"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[a mis à jour] le dépôt"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[téléchargée] archive depuis le dépôt"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[a supprimé] le dépôt"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[a créé] l’utilisateur"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[a mis à jour] l’utilisateur"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[créé] groupe d'utilisateurs"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[mis à jour] groupe d'utilisateurs"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[a commenté] une révision du dépôt"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[a commenté] la requête de pull pour"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[a fermé] la requête de pull de"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[a pushé] dans"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[a commité via Kallithea] dans le dépôt"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[a pullé depuis un site distant] dans le dépôt"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[a pullé] depuis"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[suit maintenant] le dépôt"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[ne suit plus] le dépôt"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " et %s de plus"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Aucun fichier"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "nouveau fichier"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "suppr."
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "renommer"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1288,96 +1288,98 @@
 "probablement été créé ou renommé manuellement. Veuillez relancer "
 "l’application pour rescanner les dépôts"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d an"
 msgstr[1] "%d ans"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d mois"
 msgstr[1] "%d mois"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d jour"
 msgstr[1] "%d jours"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d heure"
 msgstr[1] "%d heures"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d minute"
 msgstr[1] "%d minutes"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d seconde"
 msgstr[1] "%d secondes"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "dans %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "Il y a %s"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "dans %s et %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "Il y a %s et %s"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "à l’instant"
 
@@ -1386,158 +1388,158 @@
 msgid "on line %s"
 msgstr "à la ligne %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Mention]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "niveau supérieur"
 
-#: kallithea/model/db.py:1637
+#: kallithea/model/db.py:1634
 msgid "Kallithea Administrator"
 msgstr "Administrateur Kallithea"
 
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux dépôts"
+
+#: kallithea/model/db.py:1637
+msgid "Default user has read access to new repositories"
+msgstr "L'utilisateur par défaut a un accès en lecture aux nouveaux dépôts"
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
+msgstr "L'utilisateur par défaut a un accès en écriture aux nouveaux dépôts"
+
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux dépôts"
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
-msgstr "L'utilisateur par défaut a un accès en lecture aux nouveaux dépôts"
-
-#: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
-msgstr "L'utilisateur par défaut a un accès en écriture aux nouveaux dépôts"
-
-#: kallithea/model/db.py:1642
 msgid "Default user has admin access to new repositories"
 msgstr ""
 "L'utilisateur par défaut a un accès administrateur aux nouveaux dépôts"
 
-#: kallithea/model/db.py:1644
+#: kallithea/model/db.py:1641
 msgid "Default user has no access to new repository groups"
 msgstr ""
 "L'utilisateur par défaut n'a pas accès aux nouveaux groupes de dépôts"
 
-#: kallithea/model/db.py:1645
+#: kallithea/model/db.py:1642
 msgid "Default user has read access to new repository groups"
 msgstr ""
 "L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes de "
 "dépôts"
 
-#: kallithea/model/db.py:1646
+#: kallithea/model/db.py:1643
 msgid "Default user has write access to new repository groups"
 msgstr ""
 "L'utilisateur par défaut a accès en écriture aux nouveaux groupes de "
 "dépôts"
 
-#: kallithea/model/db.py:1647
+#: kallithea/model/db.py:1644
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 "L'utilisateur par défaut a accès administrateur aux nouveaux groupes de "
 "dépôts"
 
-#: kallithea/model/db.py:1649
+#: kallithea/model/db.py:1646
 msgid "Default user has no access to new user groups"
 msgstr ""
 "L'utilisateur par défaut n'a pas accès aux nouveaux groupes d'utilisateurs"
 
-#: kallithea/model/db.py:1650
+#: kallithea/model/db.py:1647
 msgid "Default user has read access to new user groups"
 msgstr ""
 "L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1651
+#: kallithea/model/db.py:1648
 msgid "Default user has write access to new user groups"
 msgstr ""
 "L'utilisateur par défaut a accès en écriture aux nouveaux groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1652
+#: kallithea/model/db.py:1649
 msgid "Default user has admin access to new user groups"
 msgstr ""
 "L'utilisateur par défaut a un accès administrateur aux nouveaux groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1654
+#: kallithea/model/db.py:1651
 msgid "Only admins can create repository groups"
 msgstr "Seul un administrateur peut créer un groupe de dépôts"
 
-#: kallithea/model/db.py:1655
+#: kallithea/model/db.py:1652
 msgid "Non-admins can create repository groups"
 msgstr ""
 "Les utilisateurs non-administrateurs peuvent créer des groupes de dépôts"
 
-#: kallithea/model/db.py:1657
+#: kallithea/model/db.py:1654
 msgid "Only admins can create user groups"
 msgstr "Seul un administrateur peut créer des groupes d'utilisateurs"
 
-#: kallithea/model/db.py:1658
+#: kallithea/model/db.py:1655
 msgid "Non-admins can create user groups"
 msgstr ""
 "Les utilisateurs non-administrateurs peuvent créer des groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1660
+#: kallithea/model/db.py:1657
 msgid "Only admins can create top level repositories"
 msgstr "Seul un administrateur peut créer des dépôts de niveau supérieur"
 
-#: kallithea/model/db.py:1661
+#: kallithea/model/db.py:1658
 msgid "Non-admins can create top level repositories"
 msgstr ""
 "Les utilisateurs non-administrateurs peuvent créer des dépôts de niveau "
 "supérieur"
 
-#: kallithea/model/db.py:1663
+#: kallithea/model/db.py:1660
 msgid ""
 "Repository creation enabled with write permission to a repository group"
 msgstr ""
 "Création de dépôts activée avec l'accès en écriture vers un groupe de "
 "dépôts"
 
-#: kallithea/model/db.py:1664
+#: kallithea/model/db.py:1661
 msgid ""
 "Repository creation disabled with write permission to a repository group"
 msgstr ""
 "Création de dépôts désactivée avec l'accès en écriture vers un groupe de "
 "dépôts"
 
-#: kallithea/model/db.py:1666
+#: kallithea/model/db.py:1663
 msgid "Only admins can fork repositories"
 msgstr "Seul un administrateur peut faire un fork de dépôt"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 msgid "Non-admins can fork repositories"
 msgstr "Les utilisateurs non-administrateurs peuvent faire un fork de dépôt"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "Enregistrement désactivé"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr "Enregistrement des utilisateurs avec activation de compte manuelle"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 "Enregistrement des utilisateurs avec activation de compte automatique"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "Pas encore relue"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "En cours de relecture"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 msgid "Not approved"
 msgstr "Non approuvée"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Approuvée"
 
@@ -1563,7 +1565,7 @@
 msgid "Name must not contain only digits"
 msgstr "Le nom ne doit pas contenir seulement des chiffres"
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
@@ -1572,12 +1574,12 @@
 "[Commentaire] Changeset %(short_id)s « %(message_short)s » de "
 "%(repo_name)s dans %(branch)s"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Nouvel utilisateur %(new_username)s enregistré"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
@@ -1586,7 +1588,7 @@
 "[Revue] %(repo_name)s PR %(pr_nice_id)s « %(pr_title_short)s » depuis "
 "%(pr_source_branch)s par %(pr_owner_username)s"
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
@@ -1595,11 +1597,11 @@
 "[Commentaire] %(repo_name)s PR %(pr_nice_id)s « %(pr_title_short)s » "
 "depuis %(pr_source_branch)s par %(pr_owner_username)s"
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "Fermeture"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
@@ -1607,11 +1609,11 @@
 "%(user)s veut que vous regardiez la demande de pull %(pr_nice_id)s : "
 "%(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr "Impossible de créer une requête de pull vide"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
@@ -1620,24 +1622,24 @@
 "Impossible de créer la requête de pull : fusion croisée détectée, merci "
 "de fusionner une révision plus vieille de %s vers %s"
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr "Vous n'êtes pas autorisé à créer cette requête de pull"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr "Changeset manquant depuis la précédente itération :"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "Nouveau changeset sur %s %s depuis la précédente itération :"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr "L'ancêtre n'a pas changé - diff depuis l'itération précédente :"
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
@@ -1646,48 +1648,48 @@
 "Cette itération est basée sur une autre révision %s et il n'y a pas de "
 "diff simple."
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr "Aucun changement constaté sur %s %s depuis l'itération précédente."
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr "Fermé, itération suivante : %s."
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "Dernier sommet"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset %s not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Ensemble de changements %s non trouvé"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "Nouveau enregistrement d'utilisateur"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Vous ne pouvez pas supprimer cet utilisateur ; il est nécessaire pour le "
 "bon fonctionnement de l’application"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1696,7 +1698,7 @@
 "L’utilisateur \"%s\" possède %s dépôts et ne peut être supprimé. Changez "
 "les propriétaires ou supprimez ces dépôts : %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1705,7 +1707,7 @@
 "L’utilisateur \"%s\" possède %s groupes de dépôt et ne peut être "
 "supprimé. Changez les propriétaires ou supprimez ces dépôts : %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1715,15 +1717,15 @@
 "être supprimé. Changez les propriétaires de ces groupes d'utilisateurs ou "
 "supprimez-les : %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "Lien de remise à zéro du mot de passe"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr "Notification de réinitialisation du mot de passe"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2447,7 +2449,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3579,7 +3581,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "Enregistrer les options"
 
@@ -3892,11 +3894,11 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr "Taille de la page du dépôt"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
@@ -3904,11 +3906,11 @@
 "Nombre d'éléments affichés dans les pages des dépôts avant d'afficher la "
 "pagination."
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr "Taille de la page d'admin"
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
@@ -3916,27 +3918,27 @@
 "Nombre d'éléments affichés dans les grilles des pages admin avant "
 "d'afficher la pagination."
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Icônes"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Afficher l’icône de dépôt public sur les dépôts"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Afficher l’icône de dépôt privé sur les dépôts"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr "Afficher l’icône « public/privé » à côté du nom des dépôts."
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr "Meta-tagging"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
@@ -3944,7 +3946,7 @@
 "Analyser les méta-tags dans le champ de description du dépôt et les "
 "transformer en tags colorés."
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr "Styliser les méta-tags reconnus :"
 
@@ -4581,23 +4583,23 @@
 msgid "Merge"
 msgstr "Fusion"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr "Grafté depuis :"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr "Transplanté depuis :"
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr "Remplacé par :"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr "Précédé par :"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4606,7 +4608,7 @@
 msgstr[0] "%s fichier changé"
 msgstr[1] "%s fichiers changés"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4615,8 +4617,8 @@
 msgstr[0] "%s fichier changé avec %s insertions et %s suppressions"
 msgstr[1] "%s fichiers changés avec %s insertions et %s suppressions"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5605,45 +5607,45 @@
 msgid "Stats gathered: "
 msgstr "Statistiques obtenues : "
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "Fichiers"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Afficher plus"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "commits"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "fichiers ajoutés"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "fichiers modifiés"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "fichiers supprimés"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "fichiers supprimés"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "fichier ajouté"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "fichié modifié"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "fichier supprimé"
 
--- a/kallithea/i18n/how_to	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/how_to	Thu Feb 06 01:19:23 2020 +0100
@@ -55,9 +55,9 @@
 
 First update the translation strings::
 
-    python2 setup.py extract_messages
+    python3 setup.py extract_messages
 
-Then regenerate the translation files. This could either be done with `python2
+Then regenerate the translation files. This could either be done with `python3
 setup.py update_catalog` or with `msgmerge` from the `gettext` package. As
 Weblate is also touching these translation files, it is preferred to use the
 same tools (`msgmerge`) and settings as Weblate to minimize the diff::
@@ -73,11 +73,11 @@
 In the prepared development environment, run the following to ensure
 all translation strings are extracted and up-to-date::
 
-    python2 setup.py extract_messages
+    python3 setup.py extract_messages
 
 Create new language by executing following command::
 
-    python2 setup.py init_catalog -l <new_language_code>
+    python3 setup.py init_catalog -l <new_language_code>
 
 This creates a new translation under directory `kallithea/i18n/<new_language_code>`
 based on the translation template file, `kallithea/i18n/kallithea.pot`.
@@ -90,7 +90,7 @@
 
 Finally, compile the translations::
 
-    python2 setup.py compile_catalog -l <new_language_code>
+    python3 setup.py compile_catalog -l <new_language_code>
 
 
 Manually updating translations
@@ -98,11 +98,11 @@
 
 Extract the latest versions of strings for translation by running::
 
-    python2 setup.py extract_messages
+    python3 setup.py extract_messages
 
 Update the PO file by doing::
 
-    python2 setup.py update_catalog -l <new_language_code>
+    python3 setup.py update_catalog -l <new_language_code>
 
 Edit the newly updated translation file. Repeat all steps after the
 `init_catalog` step from the 'new translation' instructions above.
--- a/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2015-04-11 00:59+0200\n"
 "Last-Translator: Balázs Úr <urbalazs@gmail.com>\n"
 "Language-Team: Hungarian <https://hosted.weblate.org/projects/kallithea/"
@@ -18,14 +18,14 @@
 "X-Generator: Weblate 2.3-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,36 +34,36 @@
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -76,61 +76,61 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -138,12 +138,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
@@ -161,90 +161,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr ""
 
+#: kallithea/controllers/files.py:502
+#, python-format
+msgid "Unknown revision %s"
+msgstr ""
+
 #: kallithea/controllers/files.py:504
-#, python-format
-msgid "Unknown revision %s"
+msgid "Empty repository"
 msgstr ""
 
 #: kallithea/controllers/files.py:506
-msgid "Empty repository"
-msgstr ""
-
-#: kallithea/controllers/files.py:508
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr ""
 
@@ -253,11 +253,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -269,194 +269,194 @@
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -469,80 +469,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -551,7 +551,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -561,44 +561,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr ""
 
@@ -674,11 +674,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -700,59 +700,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -878,7 +878,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -944,96 +944,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1064,170 +1064,170 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1235,96 +1235,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1333,133 +1335,133 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1485,145 +1487,145 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2312,7 +2314,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3363,7 +3365,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3603,53 +3605,53 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4278,23 +4280,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4303,7 +4305,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4312,8 +4314,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5285,45 +5287,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/ja/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/ja/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-08-27 07:23+0000\n"
 "Last-Translator: leela <53352@protonmail.com>\n"
 "Language-Team: Japanese <https://hosted.weblate.org/projects/kallithea/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.9-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "まだチェンジセットがありません"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,38 +33,38 @@
 msgid "None"
 msgstr "なし"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(閉鎖済み)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "空白を表示"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "空白を無視"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "diff コンテキストを %(num)s 行増やす"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "プルリクエストステータスを変更する権限がありません"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, fuzzy, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "プルリクエストの削除に成功しました"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "お探しのリビジョンはこのリポジトリにはありません"
 
@@ -80,49 +80,49 @@
 msgid "Cannot compare repositories of different types"
 msgstr "共通の祖先を持たないのでリポジトリを比較できません"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "共通の祖先を持たないのでリポジトリを比較できません"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "応答がありません"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "不明なエラー"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "形式が間違っているため、サーバーはリクエストを処理できませんでした。"
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "リソースにアクセスする権限がありません"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "このページを閲覧する権限がありません"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "リソースが見つかりません"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -130,14 +130,14 @@
 "サーバーが不正な状態になったため、リクエストに答えることができませんでし"
 "た。"
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s が %s にコミット"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -145,12 +145,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "チェンジセットが大きすぎるため、省略しました..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s フィード"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "%s リポジトリでの変更"
@@ -170,92 +170,92 @@
 msgid "%s at %s"
 msgstr "%s と %s の間"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr "有効なブランチ上のリビジョンからしかファイルを削除できません"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Kallithea経由で %s を削除"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "%s ファイルの削除に成功しました"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "コミット中にエラーが発生しました"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 #, fuzzy
 msgid "You can only edit files with revision being a valid branch"
 msgstr "有効なブランチを示すリビジョンでのみファイルを編集できます "
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Kallithea経由で %s を変更"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "変更点なし"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "%s へのコミットが成功しました"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Kallithea経由でファイルを追加"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "内容がありません"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "ファイル名がありません"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "場所には相対パスかつ .. を含まないパスを入力してください"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "ダウンロードは無効化されています"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "%s は未知のリビジョンです"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "空のリポジトリ"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "未知のアーカイブ種別です"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "チェンジセット"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "ブランチ"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "タグ"
 
@@ -264,11 +264,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "リポジトリ %s のフォーク中にエラーが発生しました"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "グループ"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -280,203 +280,203 @@
 msgid "Repositories"
 msgstr "リポジトリ"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "ブランチ"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "閉鎖済みブランチ"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "タグ"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "ブックマーク"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "公開ジャーナル"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "ジャーナル"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "キャプチャが一致しません"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "%sへの登録を受け付けました"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "パスワードリセットの確認コードが送信されました"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "無効なパスワードリセットトークン"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "パスワードを更新しました"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (閉鎖済み)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "チェンジセット"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "スペシャル"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "相手のブランチ"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "ブックマーク"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "プルリクエスト作成中にエラーが発生しました: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "プルリクエストの作成中にエラーが発生しました"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "新しいプルリクエストの作成に成功しました"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "プルリクエストレビュアー"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "説明がありません"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "プルリクエストを更新しました"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "プルリクエストの削除に成功しました"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。"
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 "このプルリクエストはすでにクローズされていて、更新することはできません。"
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 #, fuzzy
 #| msgid "No changesets found for updating this pull request."
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。"
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "ノート: ブランチ%sには別のヘッド%sがあります。"
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 #, fuzzy
 #| msgid "Git pull requests don't support updates yet."
 msgid "Git pull requests don't support iterating yet."
 msgstr "Gitのプルリクエストはまだ更新をサポートしていません。"
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。"
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "無効な検索クエリーです。\\\"で囲んで下さい。"
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "検索を実行する際にエラーが発生しました。"
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "まだデータの準備ができていません"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "このリポジトリの統計は無効化されています"
@@ -489,80 +489,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "認証設定の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "デフォルト設定の更新に成功しました"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "デフォルト設定の更新中にエラーが発生しました"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "永久"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 分"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 時間"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 日"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 ヶ月"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "有効期間"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "gist の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "gist %s を削除しました"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "変更しない"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Gist の内容を更新しました"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Gist データを更新しました"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Gist %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr "このユーザーはアプリケーション全体で非常に重要なので編集できません"
 
@@ -571,7 +571,7 @@
 msgstr "アカウントの更新に成功しました"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "ユーザー %s の更新中にエラーが発生しました"
@@ -581,45 +581,45 @@
 msgstr "パスワードの更新中にエラーが発生しました"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "ユーザーにメールアドレス %s を追加しました"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "メールの保存時にエラーが発生しました"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "ユーザーからメールアドレスを削除しました"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "APIキーの作成に成功しました"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "APIキーのリセットに成功しました"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "APIキーの削除に成功しました"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "APIキーの作成に成功しました"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -697,11 +697,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "自動でアカウントをアクティベートする"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "外部アカウントを手動でアクティベートする"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "外部アカウントを自動でアクティベートする"
 
@@ -723,59 +723,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "権限の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "リポジトリグループ %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "リポジトリグループ %s を作成しました"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "リポジトリグループ %s を更新しました"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "リポジトリグループ %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "このグループは %s 個のリポジトリを含んでいるため削除できません"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "このグループは %s 個のサブグループを含んでいるため削除できません"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "リポジトリグループ %s を削除しました"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "リポジトリグループ %s の削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "自分自身の管理者としての権限を取り消すことはできません"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "リポジトリグループ権限を更新しました"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "権限の取消中にエラーが発生しました"
 
@@ -903,7 +903,7 @@
 msgid "Updated VCS settings"
 msgstr "VCS設定を更新しました"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -973,97 +973,97 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Whooshの再インデックスタスクを予定に入れました"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "ユーザーグループ %s を作成しました"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "ユーザーグループ %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "ユーザーグループ %s を更新しました"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "ユーザーグループ %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "ユーザーグループの削除に成功しました"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "ユーザーグループの削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "対象に同じ物を選ぶことはできません"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "ユーザーグループ権限を更新しました"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "権限を更新しました"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "権限の保存時にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "ユーザー %s を作成しました"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "ユーザー %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "ユーザーの更新に成功しました"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "ユーザーの削除に成功しました"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "ユーザーの削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "デフォルト ユーザーを編集できません"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "ユーザーホワイトリストにIP %s を追加しました"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "IPアドレスの保存中にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "ユーザーホワイトリストからIPアドレスを削除しました"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 "このアクションを実行するためには登録済みのユーザーである必要があります"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "このページを閲覧するためにはサインインが必要です"
 
@@ -1099,171 +1099,171 @@
 "チェンジセットが大きすぎるため省略しました。差分を表示する場合は差分メ"
 "ニューを使用してください"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "検出された変更はありません"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "削除されたブランチ: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "作成したタグ: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "リビジョンが見つかりません"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "%s から %s までのすべてのチェンジセットを表示"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "比較ビュー"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "と"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s 以上"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "リビジョン"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "フォーク名 %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "プルリクエスト #%s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "リポジトリを[削除]"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "リポジトリを[作成]"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "フォークしてリポジトリを[作成]"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "リポジトリを[フォーク]"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "リポジトリを[更新]"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "リポジトリからアーカイブを[ダウンロード]"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "リポジトリを[削除]"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "ユーザーを[作成]"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "ユーザーを[更新]"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "ユーザーグループを[作成]"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "ユーザーグループを[更新]"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "リポジトリのリビジョンに[コメント]"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "プルリクエストに[コメント]"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "プルリクエストを[クローズ]"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[プッシュ]"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "リポジトリに[Kallithea経由でコミット]"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "リポジトリに[リモートからプル]"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[プル]"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "リポジトリの[フォローを開始]"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "リポジトリの[フォローを停止]"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " と %s 以上"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "ファイルはありません"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "新しいファイル"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "変更"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "削除"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "リネーム"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1274,90 +1274,92 @@
 "られたか名前が変更されたためです。リポジトリをもう一度チェックするためにア"
 "プリケーションを再起動してください"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d 年"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d ヶ月"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d 日"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d 時間"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d 分"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d 秒"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "%s 以内"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s 前"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "%s と %s の間"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s と %s 前"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "たったいま"
 
@@ -1366,140 +1368,140 @@
 msgid "on line %s"
 msgstr "%s 行目"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Mention]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "top level"
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr "Kallithea 管理者"
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr "デフォルトユーザーは新しいリポジトリにアクセスできません"
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
-msgstr "Kallithea 管理者"
-
-#: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr "デフォルトユーザーは新しいリポジトリにアクセスできません"
-
-#: kallithea/model/db.py:1640
 msgid "Default user has read access to new repositories"
 msgstr ""
 "デフォルトユーザーは新しいリポジトリに読み取りアクセスする権限があります"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 msgid "Default user has write access to new repositories"
 msgstr ""
 "デフォルトユーザーは新しいリポジトリに書き込みアクセスする権限があります"
 
+#: kallithea/model/db.py:1639
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1641
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr ""
+msgid "Only admins can create repository groups"
+msgstr "管理者のみがリポジトリのグループを作成できます"
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
-msgstr ""
+msgid "Non-admins can create repository groups"
+msgstr "非管理者がリポジトリのグループを作成できます"
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
-msgstr "管理者のみがリポジトリのグループを作成できます"
+msgid "Only admins can create user groups"
+msgstr "管理者だけがユーザー グループを作成することができます"
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
-msgstr "非管理者がリポジトリのグループを作成できます"
+msgid "Non-admins can create user groups"
+msgstr "非管理者ユーザーがグループを作成することができます"
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
-msgstr "管理者だけがユーザー グループを作成することができます"
+msgid "Only admins can create top level repositories"
+msgstr "管理者だけがトップレベルにリポジトリを作成することができます"
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
-msgstr "非管理者ユーザーがグループを作成することができます"
+msgid "Non-admins can create top level repositories"
+msgstr "非管理者がトップレベルにリポジトリを作成することができます"
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
-msgstr "管理者だけがトップレベルにリポジトリを作成することができます"
-
-#: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
-msgstr "非管理者がトップレベルにリポジトリを作成することができます"
-
-#: kallithea/model/db.py:1663
 msgid ""
 "Repository creation enabled with write permission to a repository group"
 msgstr ""
 "リポジトリグループの書き込みパーミッションを使ったリポジトリ作成が有効です"
 
-#: kallithea/model/db.py:1664
+#: kallithea/model/db.py:1661
 msgid ""
 "Repository creation disabled with write permission to a repository group"
 msgstr ""
 "リポジトリグループの書き込みパーミッションを使ったリポジトリ作成は無効です"
 
-#: kallithea/model/db.py:1666
+#: kallithea/model/db.py:1663
 msgid "Only admins can fork repositories"
 msgstr "管理者のみがリポジトリをフォークすることができます"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "非管理者がリポジトリをフォークすることができます"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "新規登録を無効にする"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr "ユーザーの新規登録時に手動でアカウントをアクティベートする"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr "ユーザーの新規登録時に自動でアカウントをアクティベートする"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "未レビュー"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "レビュー中"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 #, fuzzy
 #| msgid "Approved"
 msgid "Not approved"
 msgstr "承認"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "承認"
 
@@ -1525,7 +1527,7 @@
 msgid "Name must not contain only digits"
 msgstr "数字だけの名前は使えません"
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, fuzzy, python-format
 #| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
 msgid ""
@@ -1533,30 +1535,30 @@
 "%(branch)s"
 msgstr "プルリクエストに[コメント]"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "新しいユーザー %(new_username)s が登録されました"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "クローズ"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
@@ -1564,80 +1566,80 @@
 "%(user)s がプリリクエスト #%(pr_nice_id)s: %(pr_title)s のレビューを求めて"
 "います"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Error creating pull request: %s"
 msgid "Cannot create empty pull request"
 msgstr "プルリクエスト作成中にエラーが発生しました: %s"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 #, fuzzy
 #| msgid "Confirm to delete this pull request"
 msgid "You are not authorized to create the pull request"
 msgstr "このプルリクエストを削除してもよろしいですか?"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "最新のtip"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "リビジョンが見つかりません"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "新規ユーザー登録"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 #, fuzzy
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
@@ -1645,7 +1647,7 @@
 "このユーザーを削除できません。このユーザーはアプリケーションにとって必要不"
 "可欠です。"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1654,7 +1656,7 @@
 "ユーザー \"%s\" はまだ %s 個のリポジトリの所有者のため削除することはできま"
 "せん。リポジトリの所有者を変更するか削除してください: %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1663,7 +1665,7 @@
 "ユーザー \"%s\" はまだ %s 個のリポジトリグループの所有者のため削除すること"
 "はできません。リポジトリグループの所有者を変更するか削除してください: %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1672,15 +1674,15 @@
 "ユーザー \"%s\" はまだ %s 個のユーザーグループの所有者のため削除することは"
 "できません。ユーザーグループの所有者を変更するか削除してください。 %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "パスワードリセットのリンク"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr "パスワードの再設定通知"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2392,7 +2394,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3502,7 +3504,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "設定を保存"
 
@@ -3799,13 +3801,13 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repository Size"
 msgid "Repository page size"
 msgstr "リポジトリサイズ"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 #, fuzzy
 #| msgid ""
 #| "Number of items displayed in the admin pages grids before pagination "
@@ -3815,45 +3817,45 @@
 "shown."
 msgstr "管理ページで、ページ分割しないでグリッドに表示する項目の数"
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 #, fuzzy
 #| msgid "Admin pages items"
 msgid "Admin page size"
 msgstr "管理ページの項目"
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr "管理ページで、ページ分割しないでグリッドに表示する項目の数"
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "アイコン"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "公開リポジトリのアイコンを表示する"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "非公開リポジトリのアイコンを表示する"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr "リポジトリ名の隣に公開/非公開アイコンを表示します。"
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr "メタタグ"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr "リポジトリの説明のメタタグを解析して色つきのタグに変換します。"
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr "次のメタタグを変換する:"
 
@@ -4496,26 +4498,26 @@
 msgid "Merge"
 msgstr "マージ"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 #, fuzzy
 msgid "Grafted from:"
 msgstr "作成日"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 #, fuzzy
 msgid "Replaced by:"
 msgstr "作成日"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 #, fuzzy
 msgid "Preceded by:"
 msgstr "作成日"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4523,7 +4525,7 @@
 msgid_plural "%s files changed"
 msgstr[0] "%s ファイルに影響"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4531,8 +4533,8 @@
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "%s ファイルに影響。 %s 個の追加と %s 個の削除"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5547,45 +5549,45 @@
 msgid "Stats gathered: "
 msgstr "収集した統計情報: "
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "ファイル"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "もっと表示"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "コミット"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "追加されたファイル"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "変更されたファイル"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "削除されたファイル"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "削除されたファイル"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "コミット"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "追加されたファイル"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "変更されたファイル"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "削除されたファイル"
 
--- a/kallithea/i18n/kallithea.pot	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/kallithea.pot	Thu Feb 06 01:19:23 2020 +0100
@@ -1,14 +1,14 @@
 # Translations template for Kallithea.
-# Copyright (C) 2019 Various authors, licensing as GPLv3
+# Copyright (C) 2020 Various authors, licensing as GPLv3
 # This file is distributed under the same license as the Kallithea project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
 #
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: Kallithea 0.4.99\n"
+"Project-Id-Version: Kallithea 0.5.99\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\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"
@@ -18,14 +18,14 @@
 "Generated-By: Babel 2.7.0\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,35 +34,35 @@
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:88 kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89 kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -75,60 +75,60 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -136,12 +136,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
@@ -159,90 +159,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr ""
 
+#: kallithea/controllers/files.py:502
+#, python-format
+msgid "Unknown revision %s"
+msgstr ""
+
 #: kallithea/controllers/files.py:504
-#, python-format
-msgid "Unknown revision %s"
+msgid "Empty repository"
 msgstr ""
 
 #: kallithea/controllers/files.py:506
-msgid "Empty repository"
-msgstr ""
-
-#: kallithea/controllers/files.py:508
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:730 kallithea/controllers/pullrequests.py:182
-#: kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727 kallithea/controllers/pullrequests.py:174
+#: kallithea/model/scm.py:663
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:731 kallithea/controllers/pullrequests.py:183
-#: kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728 kallithea/controllers/pullrequests.py:175
+#: kallithea/model/scm.py:674
 msgid "Tags"
 msgstr ""
 
@@ -251,11 +251,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -266,193 +266,193 @@
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -465,80 +465,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -547,7 +547,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -557,44 +557,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr ""
 
@@ -670,11 +670,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -696,59 +696,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -874,7 +874,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -939,96 +939,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1057,169 +1057,169 @@
 msgid "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742 kallithea/templates/changelog/changelog.html:43
+#: kallithea/lib/helpers.py:735 kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1227,96 +1227,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1325,131 +1327,131 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid "Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid "Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid "Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid "Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1475,143 +1477,143 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch"
 " owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2299,7 +2301,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3337,7 +3339,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3576,53 +3578,53 @@
 "'ssh://{system_user}@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4246,23 +4248,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4271,7 +4273,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4280,8 +4282,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5241,45 +5243,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/lb/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/lb/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -1,17 +1,5417 @@
 # Copyright (C) 2020 Various authors, licensing as GPLv3
 # This file is distributed under the same license as the Kallithea project.
-
 msgid ""
 msgstr ""
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "Language: lb\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Et sinn nach keng Ännerungen do"
 
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/changelog.py:136
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
+#: kallithea/templates/base/perms_summary.html:14
+msgid "None"
+msgstr ""
+
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(Zou)"
+
+#: kallithea/controllers/changeset.py:82
+msgid "Show whitespace"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
+#: kallithea/templates/files/diff_2way.html:55
+msgid "Ignore whitespace"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:162
+#, python-format
+msgid "Increase diff context to %(num)s lines"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:202
+msgid "No permission to change status"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:213
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
+msgid "Such revision does not exist for this repository"
+msgstr ""
+
+#: kallithea/controllers/compare.py:68
+#, python-format
+msgid "Could not find other repository %s"
+msgstr ""
+
+#: kallithea/controllers/compare.py:74
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:247
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:249
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:253
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:269
+msgid "Cannot compare repositories without using common ancestor"
+msgstr ""
+
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
+#: kallithea/controllers/error.py:71
+msgid "Unknown error"
+msgstr ""
+
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+
+#: kallithea/controllers/error.py:87
+msgid "Unauthorized access to resource"
+msgstr ""
+
+#: kallithea/controllers/error.py:89
+msgid "You don't have permission to view this page"
+msgstr ""
+
+#: kallithea/controllers/error.py:91
+msgid "The resource could not be found"
+msgstr ""
+
+#: kallithea/controllers/error.py:93
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+
+#: kallithea/controllers/feed.py:59
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:107
+#, python-format
+msgid "%s %s feed"
+msgstr ""
+
+#: kallithea/controllers/feed.py:109
+#, python-format
+msgid "Changes on %s repository"
+msgstr ""
+
+#: kallithea/controllers/files.py:85
+msgid "Click here to add new file"
+msgstr ""
+
+#: kallithea/controllers/files.py:86
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "There are no files yet."
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/controllers/files.py:186
+#, python-format
+msgid "%s at %s"
+msgstr ""
+
+#: kallithea/controllers/files.py:295
+msgid "You can only delete files with revision being a valid branch"
+msgstr ""
+
+#: kallithea/controllers/files.py:306
+#, python-format
+msgid "Deleted file %s via Kallithea"
+msgstr ""
+
+#: kallithea/controllers/files.py:330
+#, python-format
+msgid "Successfully deleted file %s"
+msgstr ""
+
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
+msgid "Error occurred during commit"
+msgstr ""
+
+#: kallithea/controllers/files.py:349
+msgid "You can only edit files with revision being a valid branch"
+msgstr ""
+
+#: kallithea/controllers/files.py:363
+#, python-format
+msgid "Edited file %s via Kallithea"
+msgstr ""
+
+#: kallithea/controllers/files.py:378
+msgid "No changes"
+msgstr ""
+
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
+#, python-format
+msgid "Successfully committed to %s"
+msgstr ""
+
+#: kallithea/controllers/files.py:407
+msgid "Added file via Kallithea"
+msgstr ""
+
+#: kallithea/controllers/files.py:428
+msgid "No content"
+msgstr ""
+
+#: kallithea/controllers/files.py:432
+msgid "No filename"
+msgstr ""
+
+#: kallithea/controllers/files.py:459
+msgid "Location must be relative path and must not contain .. in path"
+msgstr ""
+
+#: kallithea/controllers/files.py:491
+msgid "Downloads disabled"
+msgstr ""
+
+#: kallithea/controllers/files.py:502
+#, python-format
+msgid "Unknown revision %s"
+msgstr ""
+
+#: kallithea/controllers/files.py:504
+msgid "Empty repository"
+msgstr ""
+
+#: kallithea/controllers/files.py:506
+msgid "Unknown archive type"
+msgstr ""
+
+#: kallithea/controllers/files.py:726
+#: kallithea/templates/changeset/changeset_range.html:9
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
+msgid "Changesets"
+msgstr ""
+
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
+msgid "Branches"
+msgstr ""
+
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
+msgid "Tags"
+msgstr ""
+
+#: kallithea/controllers/forks.py:174
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr ""
+
+#: kallithea/controllers/home.py:77
+msgid "Groups"
+msgstr ""
+
+#: kallithea/controllers/home.py:87
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
+#: kallithea/templates/admin/repos/repo_add.html:12
+#: kallithea/templates/admin/repos/repo_add.html:16
+#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/users/user_edit_advanced.html:6
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:437 kallithea/templates/index.html:5
+msgid "Repositories"
+msgstr ""
+
+#: kallithea/controllers/home.py:119
+#: kallithea/templates/files/files_add.html:32
+#: kallithea/templates/files/files_delete.html:23
+#: kallithea/templates/files/files_edit.html:32
+msgid "Branch"
+msgstr ""
+
+#: kallithea/controllers/home.py:125
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:131
+msgid "Tag"
+msgstr ""
+
+#: kallithea/controllers/home.py:137
+msgid "Bookmark"
+msgstr ""
+
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
+#: kallithea/templates/journal/public_journal.html:4
+#: kallithea/templates/journal/public_journal.html:18
+msgid "Public Journal"
+msgstr ""
+
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
+#: kallithea/templates/base/base.html:290
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
+msgid "Journal"
+msgstr ""
+
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
+msgid "Bad captcha"
+msgstr ""
+
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
+msgid "A password reset confirmation code has been sent"
+msgstr ""
+
+#: kallithea/controllers/login.py:236
+msgid "Invalid password reset token"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:157
+#: kallithea/controllers/login.py:241
+msgid "Successfully updated password"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:67
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:123
+#, fuzzy, python-format
+#| msgid "(closed)"
+msgid "%s (closed)"
+msgstr "(Zou)"
+
+#: kallithea/controllers/pullrequests.py:150
+#: kallithea/templates/changeset/changeset.html:12
+msgid "Changeset"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:171
+msgid "Special"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:172
+msgid "Peer branches"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
+msgid "Bookmarks"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:315
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
+msgid "Error occurred while creating pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:347
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:370
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:398
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:402
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
+msgid "No description"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:427
+msgid "Pull request updated"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:440
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:476
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:504
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:520
+msgid "This pull request has been closed and can not be updated."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:539
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:560
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:562
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:586
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:139
+msgid "An error occurred during search operation."
+msgstr ""
+
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
+msgid "No data ready yet"
+msgstr ""
+
+#: kallithea/controllers/summary.py:172
+#: kallithea/templates/summary/summary.html:97
+msgid "Statistics are disabled for this repository"
+msgstr ""
+
+#: kallithea/controllers/admin/auth_settings.py:137
+msgid "Auth settings updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/auth_settings.py:148
+msgid "error occurred during update of auth settings"
+msgstr ""
+
+#: kallithea/controllers/admin/defaults.py:74
+msgid "Default settings updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/defaults.py:89
+msgid "Error occurred during update of defaults"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:246
+msgid "Forever"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:247
+msgid "5 minutes"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:248
+msgid "1 hour"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:235
+#: kallithea/controllers/admin/users.py:249
+msgid "1 day"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:63
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:250
+msgid "1 month"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:67
+#: kallithea/controllers/admin/my_account.py:238
+#: kallithea/controllers/admin/users.py:252
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
+msgid "Lifetime"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:148
+msgid "Error occurred during gist creation"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:164
+#, python-format
+msgid "Deleted gist %s"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:207
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:237
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:242
+msgid "Successfully updated gist data"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:245
+#, python-format
+msgid "Error occurred during update of gist %s"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:119
+msgid "Your account was updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:134
+#: kallithea/controllers/admin/users.py:179
+#, python-format
+msgid "Error occurred during update of user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:168
+msgid "Error occurred during update of user password"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:209
+#: kallithea/controllers/admin/users.py:365
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:215
+#: kallithea/controllers/admin/users.py:371
+msgid "An error occurred during email saving"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:224
+#: kallithea/controllers/admin/users.py:381
+msgid "Removed email from user"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:248
+#: kallithea/controllers/admin/users.py:269
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:257
+#: kallithea/controllers/admin/users.py:279
+msgid "API key successfully reset"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:261
+#: kallithea/controllers/admin/users.py:283
+msgid "API key successfully deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:281
+#: kallithea/controllers/admin/users.py:454
+#, python-format
+msgid "SSH key %s successfully added"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:293
+#: kallithea/controllers/admin/users.py:468
+msgid "SSH key successfully deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: 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:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:74
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:75
+#: kallithea/templates/admin/auth/auth_settings.html:9
+#: kallithea/templates/admin/defaults/defaults.html:9
+#: 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_group_edit_perms.html:47
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/admin/repos/repo_add.html:10
+#: kallithea/templates/admin/repos/repo_add.html:14
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: 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_group_edit_perms.html:47
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/admin/users/user_add.html:8
+#: kallithea/templates/admin/users/user_edit.html:9
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:320
+#: kallithea/templates/base/base.html:321
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/perms_summary.html:17
+msgid "Admin"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+#: kallithea/controllers/admin/permissions.py:89
+#: kallithea/controllers/admin/permissions.py:94
+#: kallithea/controllers/admin/permissions.py:97
+#: kallithea/controllers/admin/permissions.py:100
+#: kallithea/controllers/admin/permissions.py:103
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:82
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:93
+#: kallithea/controllers/admin/permissions.py:98
+#: kallithea/controllers/admin/permissions.py:101
+#: kallithea/controllers/admin/permissions.py:104
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
+msgid "Enabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:127
+msgid "Global permissions updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:142
+msgid "Error occurred during update of permissions"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:167
+#, python-format
+msgid "Error occurred during creation of repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:174
+#, python-format
+msgid "Created repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:221
+#, python-format
+msgid "Updated repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:237
+#, python-format
+msgid "Error occurred during update of repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:247
+#, python-format
+msgid "This group contains %s repositories and cannot be deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:254
+#, python-format
+msgid "This group contains %s subgroups and cannot be deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:260
+#, python-format
+msgid "Removed repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:265
+#, python-format
+msgid "Error occurred during deletion of repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
+msgid "Cannot revoke permission for yourself as admin"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:364
+msgid "Repository group permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:396
+#: kallithea/controllers/admin/repos.py:358
+#: kallithea/controllers/admin/user_groups.py:304
+msgid "An error occurred during revoking of permission"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:136
+#, python-format
+msgid "Error creating repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:194
+#, python-format
+msgid "Created repository %s from %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:203
+#, python-format
+msgid "Forked repository %s as %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:206
+#, python-format
+msgid "Created repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:235
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:255
+#, python-format
+msgid "Error occurred during update of repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:273
+#, python-format
+msgid "Detached %s forks"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:276
+#, python-format
+msgid "Deleted %s forks"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:281
+#, python-format
+msgid "Deleted repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:284
+#, python-format
+msgid "Cannot delete repository %s which still has forks"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:289
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:329
+msgid "Repository permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:388
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:391
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:402
+msgid "An error occurred during removal of field"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:416
+msgid "-- Not a fork --"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:448
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:452
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:470
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:477
+msgid "An error occurred during this operation"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:490
+msgid "Cache invalidation successful"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:494
+msgid "An error occurred during cache invalidation"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:507
+msgid "Pulled from remote location"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:510
+msgid "An error occurred during pull from remote location"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:541
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:131
+msgid "Updated VCS settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:141
+#: kallithea/controllers/admin/settings.py:233
+msgid "Error occurred while updating application settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:176
+#, python-format
+msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:188
+#, python-format
+msgid "Invalidated %s repositories"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:229
+msgid "Updated application settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:283
+msgid "Updated visualisation settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:288
+msgid "Error occurred during updating visualisation settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:312
+msgid "Please enter email address"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:327
+msgid "Send email task created"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
+msgid "Added new hook"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:376
+msgid "Updated hooks"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:380
+msgid "Error occurred during hook creation"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:404
+msgid "Whoosh reindex task scheduled"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:136
+#, python-format
+msgid "Created user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:149
+#, python-format
+msgid "Error occurred during creation of user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:177
+#, python-format
+msgid "Updated user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:199
+#, python-format
+msgid "Error occurred during update of user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:210
+msgid "Successfully deleted user group"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:215
+msgid "An error occurred during deletion of user group"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:271
+msgid "Target group cannot be the same"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:277
+msgid "User group permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
+msgid "Updated permissions"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
+msgid "An error occurred during permissions saving"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:121
+#, python-format
+msgid "Created user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:136
+#, python-format
+msgid "Error occurred during creation of user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:160
+msgid "User updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:188
+msgid "Successfully deleted user"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:193
+msgid "An error occurred during deletion of user"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:201
+msgid "The default user cannot be edited"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:407
+#, python-format
+msgid "Added IP address %s to user whitelist"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:413
+msgid "An error occurred while adding IP address"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:425
+msgid "Removed IP address from user whitelist"
+msgstr ""
+
+#: kallithea/lib/auth.py:668
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:696
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:483
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:580
+msgid "Repository not found in the filesystem"
+msgstr ""
+
+#: kallithea/lib/base.py:605
+#, python-format
+msgid "Changeset for %s %s not found in %s"
+msgstr ""
+
+#: kallithea/lib/base.py:647
+msgid "SSH access is disabled."
+msgstr ""
+
+#: kallithea/lib/diffs.py:194
+msgid "Binary file"
+msgstr ""
+
+#: kallithea/lib/diffs.py:214
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
+msgid "No changes detected"
+msgstr ""
+
+#: kallithea/lib/helpers.py:646
+#, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:648
+#, python-format
+msgid "Created tag: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:659
+#, python-format
+msgid "Changeset %s not found"
+msgstr ""
+
+#: kallithea/lib/helpers.py:708
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:714
+msgid "Compare view"
+msgstr ""
+
+#: kallithea/lib/helpers.py:733
+msgid "and"
+msgstr ""
+
+#: kallithea/lib/helpers.py:734
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: kallithea/lib/helpers.py:735
+#: kallithea/templates/changelog/changelog.html:43
+msgid "revisions"
+msgstr ""
+
+#: kallithea/lib/helpers.py:759
+#, python-format
+msgid "Fork name %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:780
+#, python-format
+msgid "Pull request %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:790
+msgid "[deleted] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
+msgid "[created] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:794
+msgid "[created] repository as fork"
+msgstr ""
+
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
+msgid "[forked] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
+msgid "[updated] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:800
+msgid "[downloaded] archive from repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:802
+msgid "[delete] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:810
+msgid "[created] user"
+msgstr ""
+
+#: kallithea/lib/helpers.py:812
+msgid "[updated] user"
+msgstr ""
+
+#: kallithea/lib/helpers.py:814
+msgid "[created] user group"
+msgstr ""
+
+#: kallithea/lib/helpers.py:816
+msgid "[updated] user group"
+msgstr ""
+
+#: kallithea/lib/helpers.py:818
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:820
+msgid "[commented] on pull request for"
+msgstr ""
+
+#: kallithea/lib/helpers.py:822
+msgid "[closed] pull request for"
+msgstr ""
+
+#: kallithea/lib/helpers.py:824
+msgid "[pushed] into"
+msgstr ""
+
+#: kallithea/lib/helpers.py:826
+msgid "[committed via Kallithea] into repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:828
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:830
+msgid "[pulled] from"
+msgstr ""
+
+#: kallithea/lib/helpers.py:832
+msgid "[started following] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:834
+msgid "[stopped following] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:954
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: kallithea/lib/helpers.py:958
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
+msgid "No files"
+msgstr ""
+
+#: kallithea/lib/helpers.py:983
+msgid "new file"
+msgstr ""
+
+#: kallithea/lib/helpers.py:986
+msgid "mod"
+msgstr ""
+
+#: kallithea/lib/helpers.py:989
+msgid "del"
+msgstr ""
+
+#: kallithea/lib/helpers.py:992
+msgid "rename"
+msgstr ""
+
+#: kallithea/lib/helpers.py:997
+msgid "chmod"
+msgstr ""
+
+#: kallithea/lib/helpers.py:1290
+#, 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/ssh.py:75
+msgid "SSH key is missing"
+msgstr ""
+
+#: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
+msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
+msgstr ""
+
+#: kallithea/lib/ssh.py:86
+#, python-format
+msgid "Incorrect SSH key - unexpected characters in base64 part %r"
+msgstr ""
+
+#: kallithea/lib/ssh.py:91
+#, python-format
+msgid "Incorrect SSH key - failed to decode base64 part %r"
+msgstr ""
+
+#: kallithea/lib/ssh.py:94
+#, python-format
+msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
+msgstr ""
+
+#: kallithea/lib/utils2.py:242
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:243
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:247
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:263
+#, python-format
+msgid "in %s"
+msgstr ""
+
+#: kallithea/lib/utils2.py:265
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#: kallithea/lib/utils2.py:267
+#, python-format
+msgid "in %s and %s"
+msgstr ""
+
+#: kallithea/lib/utils2.py:270
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: kallithea/lib/utils2.py:273
+msgid "just now"
+msgstr ""
+
+#: kallithea/model/comment.py:68
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
+msgid "[Mention]"
+msgstr ""
+
+#: kallithea/model/db.py:1493
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1637
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1639
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1641
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1642
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1644
+msgid "Default user has admin access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1646
+msgid "Default user has no access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1647
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1649
+msgid "Default user has admin access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1651
+msgid "Only admins can create repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1652
+msgid "Non-admins can create repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1654
+msgid "Only admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1655
+msgid "Non-admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1657
+msgid "Only admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1658
+msgid "Non-admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1660
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1661
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1663
+msgid "Only admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1664
+msgid "Non-admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1666
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1667
+msgid "User registration with manual account activation"
+msgstr ""
+
+#: kallithea/model/db.py:1668
+msgid "User registration with automatic account activation"
+msgstr ""
+
+#: kallithea/model/db.py:2208
+msgid "Not reviewed"
+msgstr ""
+
+#: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
+msgid "Approved"
+msgstr ""
+
+#: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: kallithea/model/forms.py:170
+msgid "Name must not contain only digits"
+msgstr ""
+
+#: kallithea/model/notification.py:163
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:166
+#, python-format
+msgid "New user %(new_username)s registered"
+msgstr ""
+
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:169
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:189
+msgid "Closing"
+msgstr ""
+
+#: kallithea/model/pull_request.py:72
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:207
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:215
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:337
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:344
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:351
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:358
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:360
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:386
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:655
+msgid "latest tip"
+msgstr ""
+
+#: kallithea/model/ssh_key.py:57
+#, python-format
+msgid "SSH key %r is invalid: %s"
+msgstr ""
+
+#: kallithea/model/ssh_key.py:69
+#, python-format
+msgid "SSH key %s is already used by %s"
+msgstr ""
+
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
+msgid "New user registration"
+msgstr ""
+
+#: kallithea/model/user.py:248
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:253
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories: %s"
+msgstr ""
+
+#: kallithea/model/user.py:258
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:265
+#, python-format
+msgid ""
+"User \"%s\" still owns %s user groups and cannot be removed. Switch "
+"owners or remove those user groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:359
+msgid "Password reset link"
+msgstr ""
+
+#: kallithea/model/user.py:406
+msgid "Password reset notification"
+msgstr ""
+
+#: kallithea/model/user.py:407
+#, python-format
+msgid ""
+"The password to your account %s has been changed using password reset "
+"form."
+msgstr ""
+
+#: kallithea/model/validators.py:52 kallithea/model/validators.py:53
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: kallithea/model/validators.py:72
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:74
+#, python-format
+msgid "Username \"%(username)s\" cannot be used"
+msgstr ""
+
+#: kallithea/model/validators.py:76
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:103
+msgid "The input is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:110
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:131
+msgid "Invalid user group name"
+msgstr ""
+
+#: kallithea/model/validators.py:132
+#, python-format
+msgid "User group \"%(usergroup)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:134
+msgid ""
+"user group name may only contain alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: kallithea/model/validators.py:174
+msgid "Cannot assign this group as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:175
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:177
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:233
+msgid "Invalid characters (non-ascii) in password"
+msgstr ""
+
+#: kallithea/model/validators.py:248
+msgid "Invalid old password"
+msgstr ""
+
+#: kallithea/model/validators.py:264
+msgid "Passwords do not match"
+msgstr ""
+
+#: kallithea/model/validators.py:279
+msgid "Invalid username or password"
+msgstr ""
+
+#: kallithea/model/validators.py:313
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:315
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:316
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr ""
+
+#: kallithea/model/validators.py:318
+#, python-format
+msgid "Repository group with name \"%(repo)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:404
+msgid "Invalid repository URL"
+msgstr ""
+
+#: kallithea/model/validators.py:405
+msgid ""
+"Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
+"svn+https URL"
+msgstr ""
+
+#: kallithea/model/validators.py:430
+msgid "Fork has to be the same type as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:445
+msgid "You don't have permissions to create repository in this group"
+msgstr ""
+
+#: kallithea/model/validators.py:447
+msgid "no permission to create repository in root location"
+msgstr ""
+
+#: kallithea/model/validators.py:497
+msgid "You don't have permissions to create a group in this location"
+msgstr ""
+
+#: kallithea/model/validators.py:537
+msgid "This username or user group name is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:630
+msgid "This is not a valid path"
+msgstr ""
+
+#: kallithea/model/validators.py:647
+msgid "This email address is already in use"
+msgstr ""
+
+#: kallithea/model/validators.py:667
+#, python-format
+msgid "Email address \"%(email)s\" not found"
+msgstr ""
+
+#: kallithea/model/validators.py:704
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: kallithea/model/validators.py:716
+msgid "Please enter a valid IPv4 or IPv6 address"
+msgstr ""
+
+#: kallithea/model/validators.py:717
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:750
+msgid "Key name can only consist of letters, underscore, dash or numbers"
+msgstr ""
+
+#: kallithea/model/validators.py:764
+msgid "Filename cannot be inside a directory"
+msgstr ""
+
+#: kallithea/model/validators.py:780
+#, python-format
+msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
+msgstr ""
+
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
+msgid "About"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add.html:5
+#: kallithea/templates/admin/repos/repo_add.html:19
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
+msgid "Add Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:5
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "You have admin right to this group, and can edit it"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "Edit Repository Group"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:5
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:58
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:60
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:54
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:5
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:58
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:60
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
+#: kallithea/templates/pullrequests/pullrequest_show.html:38
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:87
+msgid "Description"
+msgstr ""
+
+#: kallithea/templates/index_base.html:60
+msgid "Last Change"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
+msgid "Tip"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:47
+#: kallithea/templates/admin/repos/repos.html:42
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
+#: kallithea/templates/pullrequests/pullrequest_data.html:16
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:132
+msgid "Owner"
+msgstr ""
+
+#: kallithea/templates/base/base.html:380 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
+msgid "Log In"
+msgstr ""
+
+#: kallithea/templates/login.html:17
+#, python-format
+msgid "Log In to %s"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:5
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:364
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
+msgid "Username"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:368 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
+msgid "Password"
+msgstr ""
+
+#: kallithea/templates/login.html:44
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
+msgid "Forgot your password ?"
+msgstr ""
+
+#: kallithea/templates/login.html:55
+msgid "Don't have an account ?"
+msgstr ""
+
+#: kallithea/templates/login.html:62
+msgid "Sign In"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:5
+msgid "Password Reset"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
+msgid "Reset Your Password to %s"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:23
+#: kallithea/templates/password_reset_confirmation.html:5
+#: kallithea/templates/password_reset_confirmation.html:18
+msgid "Reset Your Password"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:30
+msgid "Email Address"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
+msgid "Captcha"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
+msgid ""
+"A password reset link will be sent to the specified email address if it "
+"is registered in the system."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:23
+#, python-format
+msgid "You are about to set a new password for the email address %s."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:24
+msgid ""
+"Note that you must use the same browser session for this as the one used "
+"to request the password reset."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:29
+msgid "Code you received in the email"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:36
+msgid "New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:43
+msgid "Confirm New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:51
+msgid "Confirm"
+msgstr ""
+
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
+msgid "Sign Up"
+msgstr ""
+
+#: kallithea/templates/register.html:22
+#, python-format
+msgid "Sign Up to %s"
+msgstr ""
+
+#: kallithea/templates/register.html:45
+msgid "Re-enter password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
+msgid "First Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
+msgid "Last Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
+#: kallithea/templates/admin/settings/settings.html:31
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
+msgid "Email"
+msgstr ""
+
+#: kallithea/templates/register.html:85
+msgid "Registered accounts are ready to use and need no further action."
+msgstr ""
+
+#: kallithea/templates/register.html:87
+msgid "Please wait for an administrator to activate your account."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:5
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/base/base.html:55
+msgid "Admin Journal"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
+msgid "journal filter..."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:12
+#: kallithea/templates/journal/journal.html:12
+msgid "Filter"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/journal/journal.html:13
+#, python-format
+msgid "%s Entry"
+msgid_plural "%s Entries"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/admin_log.html:6
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:7
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:7
+#: kallithea/templates/admin/users/users.html:45
+msgid "Action"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:8
+msgid "Date"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:61
+msgid "No actions yet"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:5
+msgid "Authentication Settings"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:11
+#: kallithea/templates/base/base.html:61
+msgid "Authentication"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:27
+msgid "Authentication Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:29
+msgid "Enabled Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:32
+msgid ""
+"Comma-separated list of plugins; Kallithea will try user authentication "
+"in plugin order"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
+msgid "Available built-in plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:53
+msgid "Plugin"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:101
+#: kallithea/templates/admin/defaults/defaults.html:59
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:27
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:99
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:42
+msgid "Save"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:5
+#: kallithea/templates/admin/defaults/defaults.html:11
+#: kallithea/templates/base/base.html:62
+msgid "Repository Defaults"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
+msgid "Type"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:62
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:65
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:69
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:72
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:76
+msgid "Enable downloads"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:79
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:5
+#: kallithea/templates/admin/gists/edit.html:18
+msgid "Edit Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:35
+#, python-format
+msgid ""
+"Gist was updated since you started editing. Copy your changes and click "
+"%(here)s to reload new version."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:36
+msgid "here"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
+msgid "Gist description ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
+msgid "Gist lifetime"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/edit.html:61
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/gists/show.html:47
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
+msgid "Expires"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:21
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:21
+msgid "Never"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:145
+msgid "Update Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:146
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
+msgid "Cancel"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:6
+#: kallithea/templates/admin/gists/index.html:16
+#, python-format
+msgid "Private Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:8
+#: kallithea/templates/admin/gists/index.html:18
+#, python-format
+msgid "Public Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:10
+#: kallithea/templates/admin/gists/index.html:20
+msgid "Public Gists"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:37
+#: kallithea/templates/admin/gists/show.html:25
+#: kallithea/templates/base/base.html:305
+msgid "Create New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
+msgid "Created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:66
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "There are no gists yet"
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/templates/admin/gists/new.html:5
+#: kallithea/templates/admin/gists/new.html:18
+msgid "New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
+msgid "Create Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:54
+msgid "Create Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:66
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:28
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:100
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:66
+#: kallithea/templates/admin/settings/settings_visual.html:129
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:66
+#: kallithea/templates/base/default_perms_box.html:43
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
+msgid "Reset"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:5
+#: kallithea/templates/admin/gists/show.html:9
+msgid "Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:10
+msgid "URL"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:37
+msgid "Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/pullrequests/pullrequest_data.html:20
+msgid "Delete"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+msgid "Confirm to delete this Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
+msgid "Show as Raw"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:69
+msgid "created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:82
+msgid "Show as raw"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:5
+#: kallithea/templates/admin/my_account/my_account.html:9
+#: kallithea/templates/base/base.html:390
+msgid "My Account"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:25
+#: kallithea/templates/admin/users/user_edit.html:29
+msgid "Profile"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:26
+msgid "Email Addresses"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:29
+#: kallithea/templates/admin/users/user_edit.html:32
+msgid "SSH Keys"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:31
+#: kallithea/templates/admin/users/user_edit.html:34
+msgid "API Keys"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:32
+msgid "Owned Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:33
+#: kallithea/templates/journal/journal.html:33
+msgid "Watched Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:34
+#: kallithea/templates/admin/permissions/permissions.html:30
+#: kallithea/templates/admin/user_groups/user_group_edit.html:32
+#: kallithea/templates/admin/users/user_edit.html:37
+msgid "Show Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
+msgid "Built-in"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
+#, python-format
+msgid "Confirm to reset this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
+msgid "Expired"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
+#, python-format
+msgid "Confirm to remove this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:30
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:30
+msgid "Remove"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
+msgid "No additional API keys specified"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
+msgid "New API key"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:65
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:65
+msgid "Add"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
+msgid "Primary"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
+msgid "No additional emails specified."
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
+msgid "New email address"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:1
+msgid "Change Your Account Password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:8
+msgid "Current password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
+msgid "New password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:22
+msgid "Confirm new password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:12
+#: kallithea/templates/admin/users/user_edit_profile.html:12
+msgid "Avatars are disabled"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:1
+msgid "Repositories You Own"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:54
+msgid "Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:4
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:4
+msgid "Fingerprint"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:6
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:6
+msgid "Last Used"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:28
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:28
+#, python-format
+msgid "Confirm to remove this SSH key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:39
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:39
+msgid "No SSH keys have been added"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:49
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:49
+msgid "New SSH key"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:52
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:52
+msgid "Public key"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_ssh_keys.html:54
+#: kallithea/templates/admin/users/user_edit_ssh_keys.html:54
+msgid "Public key (contents of e.g. ~/.ssh/id_rsa.pub)"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_watched.html:1
+msgid "Repositories You are Watching"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:5
+#: kallithea/templates/admin/permissions/permissions.html:11
+#: kallithea/templates/base/base.html:60
+msgid "Default Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:28
+#: kallithea/templates/admin/settings/settings.html:29
+msgid "Global"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:29
+#: kallithea/templates/admin/users/user_edit.html:35
+msgid "IP Whitelist"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
+msgid "Anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
+#, python-format
+msgid ""
+"Allow access to Kallithea without needing to log in. Anonymous users use "
+"%s user permissions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
+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:20
+msgid "Apply to all existing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:33
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:32
+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:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+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:46
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
+msgid "Top level repository creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+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:61
+msgid "Repository creation with group write access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
+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:68
+msgid "User group creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
+msgid "Enable this to allow non-admins to create user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
+msgid "External auth account activation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
+msgid "Confirm to delete this IP address: %s"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
+msgid "All IP addresses are allowed."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
+msgid "New IP address"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
+msgid "Repository Groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
+msgid "Group name"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
+msgid "Group parent"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
+msgid "Copy parent group permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
+msgid "Copy permission set from parent repository group."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
+#, python-format
+msgid "%s Repository Group Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
+msgid "Add Child Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
+#: kallithea/templates/admin/repos/repo_edit.html:12
+#: kallithea/templates/admin/repos/repo_edit.html:25
+#: kallithea/templates/admin/settings/settings.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit.html:29
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
+msgid "Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
+#: kallithea/templates/admin/user_groups/user_group_edit.html:30
+#: kallithea/templates/admin/users/user_edit.html:36
+msgid "Advanced"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
+#: 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"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6
+msgid "Top level repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7
+msgid "Total repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8
+msgid "Children groups"
+msgstr ""
+
+#: 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:118
+msgid "Created on"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:121
+#, python-format
+msgid "Confirm to delete this group: %s with %s repository"
+msgid_plural "Confirm to delete this group: %s with %s repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25
+msgid "Delete this repository group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
+msgid "User/User Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
+msgid "Default"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
+msgid "Revoke"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
+msgid "Add new"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
+msgid "Apply to children"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
+msgid "Both"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
+msgid ""
+"Set or revoke permission to all children of that group, including non-"
+"private repositories and other groups if selected."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+msgid "Remove this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+msgid "Confirm to delete this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_show.html:4
+#, python-format
+msgid "Repository group %s"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:5
+msgid "Repository Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
+msgid "Number of Top-level Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:12
+msgid "Clone remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:16
+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:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:36
+#: kallithea/templates/forks/fork.html:45
+msgid "Optionally select a group to put this repository into."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:45
+msgid "Type of repository to create."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:40
+#: kallithea/templates/forks/fork.html:50
+msgid "Landing revision"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:52
+msgid ""
+"Default revision for files page, downloads, full text search index and "
+"readme generation"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:9
+#, python-format
+msgid "%s Creating Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:13
+msgid "Creating repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:27
+#, python-format
+msgid ""
+"Repository \"%(repo_name)s\" is being created, you will be redirected "
+"when this process is finished.repo_name"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:39
+msgid ""
+"We're sorry but error occurred during this operation. Please check your "
+"Kallithea server logs, or contact administrator."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:8
+#, python-format
+msgid "%s Repository Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:34
+msgid "Extra Fields"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:37
+msgid "Caches"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:40
+msgid "Remote"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/summary/statistics.html:8
+#: kallithea/templates/summary/summary.html:169
+#: kallithea/templates/summary/summary.html:170
+msgid "Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:1
+msgid "Parent"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:5
+msgid "Set"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
+msgid "Manually set this repository as a fork of another from the list."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
+msgid "Public Journal Visibility"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
+msgid "Remove from public journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
+msgid "Add to Public Journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
+msgid ""
+"All actions done in this repository will be visible to everyone in the "
+"public journal."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/data_table/_dt_elements.html:68
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Delete this Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:51
+#, python-format
+msgid "This repository has %s fork"
+msgid_plural "This repository has %s forks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+msgid "Detach forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Delete forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+msgid ""
+"The deleted repository will be moved away and hidden until the "
+"administrator expires it. The administrator can both permanently delete "
+"it or restore it."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:4
+msgid "Invalidate Repository Cache"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
+msgid ""
+"Manually invalidate cache for this repository. On first access, the "
+"repository will be cached again."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgid "Prefix"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+msgid "Key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
+msgid "Active"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+msgid "Label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
+#, python-format
+msgid "Confirm to delete this field: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
+msgid "New field key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
+msgid "New field label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
+msgid "Enter short label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
+msgid "New field description"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
+msgid "Enter description of a field"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
+msgid "Extra fields are disabled."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
+msgid "Private Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
+msgid "Remote repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
+msgid "Pull Changes from Remote Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
+msgid "This repository does not have a remote repository URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
+msgid "Permanent URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+msgid ""
+"In case this repository is renamed or moved into another group the "
+"repository URL changes.\n"
+"                               Using the above permanent URL guarantees "
+"that this repository always will be accessible on that URL.\n"
+"                               This is useful for CI systems, or any "
+"other cases that you need to hardcode the URL into a 3rd party service."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+msgid "Remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:24
+msgid "Repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+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:43
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:50
+msgid "Change owner of this repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+msgid "Processed progress"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Reset Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Confirm to remove current statistics."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:5
+msgid "Repositories Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:43
+msgid "State"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:5
+msgid "Settings Administration"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:27
+msgid "VCS"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:28
+msgid "Remap and Rescan"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:30
+msgid "Visual"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:4
+msgid "Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:33
+msgid "Full Text Search"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:34
+msgid "System Info"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:4
+msgid "Send test email to"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:12
+msgid "Send"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:4
+msgid "Site branding"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:12
+msgid "HTTP authentication realm"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid ""
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
+msgid "ReCaptcha public key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:35
+msgid "Public key for reCaptcha system."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:40
+msgid "ReCaptcha private key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:43
+msgid ""
+"Private key for reCaptcha system. Setting this value will enable captcha "
+"on registration."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:128
+msgid "Save Settings"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:3
+msgid "Built-in Mercurial Hooks (Read-Only)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid ""
+"Hooks can be used to trigger actions on certain events such as push / "
+"pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
+msgid "Failed to remove hook"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
+msgid "Delete records of missing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:12
+msgid ""
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:17
+msgid "Invalidate cache for all repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:20
+msgid "Check this to reload data and clear cache keys for all repositories."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:25
+msgid "Install Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
+msgid "Overwrite existing Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
+msgid "Rescan Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:4
+msgid "Index build option"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:12
+msgid ""
+"This option completely reindexeses all of the repositories for proper "
+"fulltext search capabilities."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:18
+msgid "Reindex"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:22
+msgid "Python Packages"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:9
+msgid "Show repository size after push"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:15
+msgid "Update repository after push (hg update)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:21
+msgid "Mercurial extensions"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:26
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:32
+msgid "Enable hgsubversion extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:35
+msgid ""
+"Requires hgsubversion library to be installed. Enables cloning of remote "
+"Subversion repositories while converting them to Mercurial."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:47
+msgid "Location of repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:52
+msgid ""
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:56
+msgid ""
+"Filesystem location where repositories are stored. After changing this "
+"value, a restart and rescan of the repository folder are both required."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
+msgid "General"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:9
+msgid "Use repository extra fields"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:12
+msgid "Allows storing additional customized fields per repository."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:17
+msgid "Show Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:20
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
+msgid ""
+"Gravatar URL allows you to use another avatar server application.\n"
+"                                                        The following "
+"variables of the URL will be replaced accordingly.\n"
+"                                                        {scheme}    "
+"'http' or 'https' sent from running Kallithea server,\n"
+"                                                        {email}     user "
+"email,\n"
+"                                                        {md5email}  md5 "
+"hash of the user email (like at gravatar.com),\n"
+"                                                        {size}      size "
+"of the image that is expected from the server application,\n"
+"                                                        {netloc}    "
+"network location/server host of running Kallithea server"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:40
+msgid "HTTP Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
+msgid ""
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
+"variables are available:\n"
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
+"location/server host of running Kallithea server,\n"
+"                                                    {repo}   full "
+"repository name,\n"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id,\n"
+"                                                    {system_user}  name "
+"of the Kallithea system user,\n"
+"                                                    {hostname}  server "
+"hostname\n"
+"                                                    "
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:56
+msgid "SSH Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:59
+msgid ""
+"Schema for constructing SSH clone URL, eg. 'ssh://{system_user}"
+"@{hostname}/{repo}'."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:67
+msgid "Repository page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid ""
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:75
+msgid "Admin page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:78
+msgid ""
+"Number of items displayed in the admin pages grids before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:83
+msgid "Icons"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:88
+msgid "Show public repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid "Show private repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:97
+msgid "Show public/private icons next to repository names."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:102
+msgid "Meta Tagging"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:107
+msgid ""
+"Parses meta tags from the repository description field and turns them "
+"into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:111
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:5
+msgid "Add user group"
+msgstr ""
+
+#: 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:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
+msgid "User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:12
+#: kallithea/templates/admin/user_groups/user_groups.html:24
+msgid "Add User Group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
+msgid "Short, optional description for this user group."
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:5
+#, python-format
+msgid "%s user group settings"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:33
+msgid "Show Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
+#, python-format
+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:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
+msgid "Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
+#: kallithea/templates/data_table/_dt_elements.html:102
+#, python-format
+msgid "Confirm to delete this user group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21
+msgid "Delete this user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
+msgid "No members yet"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
+msgid "Chosen group members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
+msgid "Available members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_groups.html:5
+msgid "User Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:10
+#: kallithea/templates/admin/users/user_edit.html:11
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
+msgid "Users"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:12
+#: kallithea/templates/admin/users/users.html:23
+msgid "Add User"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:41
+msgid "Password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:5
+#, python-format
+msgid "%s user settings"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:30
+msgid "Emails"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:1
+#, python-format
+msgid "User: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_profile.html:32
+msgid "Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:9
+#: kallithea/templates/admin/users/users.html:41
+msgid "Last Login"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:10
+msgid "Member of User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:90
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:23
+msgid "Delete this user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_ips.html:7
+#, python-format
+msgid "Inherited from %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:39
+msgid "Name in Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:53
+msgid "New password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:5
+msgid "Users Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:44
+msgid "Auth Type"
+msgstr ""
+
+#: kallithea/templates/base/base.html:16
+#, python-format
+msgid "Server instance: %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:28
+msgid "Support"
+msgstr ""
+
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:417
+msgid "Mercurial repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:420
+msgid "Git repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:115
+msgid "Create Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
+msgid "Summary"
+msgstr ""
+
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
+msgid "Changelog"
+msgstr ""
+
+#: kallithea/templates/base/base.html:133
+#: kallithea/templates/files/files.html:11
+msgid "Files"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
+msgid "Options"
+msgstr ""
+
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
+msgid "Compare Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:158
+msgid "Compare"
+msgstr ""
+
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:315
+#: kallithea/templates/search/search.html:14
+#: kallithea/templates/search/search.html:67
+msgid "Search"
+msgstr ""
+
+#: kallithea/templates/base/base.html:167
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:168
+msgid "Unfollow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:171
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:172
+#: kallithea/templates/pullrequests/pullrequest.html:77
+msgid "Create Pull Request"
+msgstr ""
+
+#: kallithea/templates/base/base.html:184
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:196
+#: kallithea/templates/base/base.html:445
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:289
+msgid "Show recent activity"
+msgstr ""
+
+#: kallithea/templates/base/base.html:295
+#: kallithea/templates/base/base.html:296
+msgid "Public journal"
+msgstr ""
+
+#: kallithea/templates/base/base.html:301
+msgid "Show public gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:302
+msgid "Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:306
+msgid "All Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:308
+msgid "My Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:309
+msgid "My Private Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:314
+msgid "Search in repositories"
+msgstr ""
+
+#: kallithea/templates/base/base.html:337
+#: kallithea/templates/base/base.html:338
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
+msgid "My Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:353
+msgid "Not Logged In"
+msgstr ""
+
+#: kallithea/templates/base/base.html:362
+msgid "Login to Your Account"
+msgstr ""
+
+#: kallithea/templates/base/base.html:372
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:376
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:393
+msgid "Log Out"
+msgstr ""
+
+#: kallithea/templates/base/base.html:517
+msgid "Parent rev."
+msgstr ""
+
+#: kallithea/templates/base/base.html:526
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
+msgid "Create repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:15
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:21
+msgid "Create user groups"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:25
+msgid "Select this option to allow user group creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:31
+msgid "Fork repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:35
+msgid "Select this option to allow repository forking for this user"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:13
+#: kallithea/templates/changelog/changelog.html:41
+msgid "Show"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:22
+msgid "No permissions defined yet"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:30
+#: kallithea/templates/base/perms_summary.html:55
+msgid "Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:32
+#: kallithea/templates/base/perms_summary.html:57
+msgid "Edit Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:92
+msgid "No permission defined"
+msgstr ""
+
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:30
+msgid "Unable to post"
+msgstr ""
+
+#: kallithea/templates/base/root.html:31
+msgid "Add Another Comment"
+msgstr ""
+
+#: kallithea/templates/base/root.html:32
+msgid "Stop following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:33
+msgid "Start following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:34
+msgid "Group"
+msgstr ""
+
+#: kallithea/templates/base/root.html:35
+msgid "Loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:36
+msgid "loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:37
+msgid "Search truncated"
+msgstr ""
+
+#: kallithea/templates/base/root.html:38
+msgid "No matching files"
+msgstr ""
+
+#: kallithea/templates/base/root.html:39
+msgid "Open New Pull Request from {0}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:40
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
+msgid "Specify changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
+#, python-format
+msgid "%s Changelog"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:23
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changelog/changelog.html:47
+msgid "Clear selection"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:54
+msgid "Go to tip of repository"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
+#, python-format
+msgid "Compare fork with %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:61
+#, python-format
+msgid "Compare fork with parent repository (%s)"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:65
+#: kallithea/templates/files/files.html:29
+msgid "Branch filter:"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:221
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "There are no changes yet"
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/templates/changelog/changelog_details.html:4
+#: kallithea/templates/changeset/changeset.html:77
+msgid "Removed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:5
+#: kallithea/templates/changeset/changeset.html:78
+msgid "Changed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:6
+#: kallithea/templates/changeset/changeset.html:79
+#: kallithea/templates/changeset/diff_block.html:38
+msgid "Added"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:8
+#: kallithea/templates/changelog/changelog_details.html:9
+#: kallithea/templates/changelog/changelog_details.html:10
+#: kallithea/templates/changeset/changeset.html:81
+#: kallithea/templates/changeset/changeset.html:82
+#: kallithea/templates/changeset/changeset.html:83
+#, python-format
+msgid "Affected %s files"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, python-format
+msgid "%s comments"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:8
+#, python-format
+msgid "%s Changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:34
+msgid "Changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
+msgid "Raw diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:46
+msgid "Patch diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
+msgid "Download diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
+msgid "Merge"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:95
+msgid "Grafted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:100
+msgid "Transplanted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:106
+msgid "Replaced by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:120
+msgid "Preceded by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:137
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
+#, python-format
+msgid "%s file changed"
+msgid_plural "%s files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
+#, python-format
+msgid "%s file changed with %s insertions and %s deletions"
+msgid_plural "%s files changed with %s insertions and %s deletions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Show full diff anyway"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+msgid "comment"
+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 "on this changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+msgid "Delete comment?"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
+msgid "Status change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+#| msgid "(closed)"
+msgid "Close"
+msgstr "(Zou)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
+msgid "Comment"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "You need to be logged in to comment."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "Login now"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:141
+msgid "Hide"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:153
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
+msgid "%d inline"
+msgid_plural "%d inline"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
+msgid "%d general"
+msgid_plural "%d general"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
+msgid "Files affected"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, python-format
+msgid "Unknown operation: %r"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "No changesets"
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
+msgid "Show merge diff"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:54
+msgid "is"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
+msgid "%s changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:56
+msgid "behind"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:6
+#: kallithea/templates/compare/compare_diff.html:8
+#, python-format
+msgid "%s Compare"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:13
+#: kallithea/templates/compare/compare_diff.html:41
+msgid "Compare Revisions"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:39
+msgid "Swap"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:48
+msgid "Compare revisions, branches, bookmarks, or tags."
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
+#, python-format
+msgid "Showing %s commit"
+msgid_plural "Showing %s commits"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/compare/compare_diff.html:95
+msgid "Show full diff"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:23
+msgid "Public repository"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:29
+msgid "Repository creation in progress..."
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:42
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "No changesets yet"
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:76
+msgid "Creating"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+msgid "Changeset on"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:9
+#, python-format
+msgid "Hello %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:16
+msgid "We have received a request to reset the password for your account."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
+msgid "To set a new password, click the following link"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:33
+msgid ""
+"Should you not be able to use the link above, please type the following "
+"code into the password reset form"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:44
+msgid ""
+"If it weren't you who requested the password reset, just disregard this "
+"message."
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+msgid "Pull request"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:15
+#, python-format
+msgid "%s File side-by-side diff"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:19
+#: kallithea/templates/files/file_diff.html:8
+msgid "File diff"
+msgstr ""
+
+#: kallithea/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File Diff"
+msgstr ""
+
+#: kallithea/templates/files/files.html:4
+#: kallithea/templates/files/files.html:74
+#, python-format
+msgid "%s Files"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:4
+#, python-format
+msgid "%s Files Add"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:199
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
+#: kallithea/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:41
+msgid "Enter filename..."
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
+msgid "or"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+msgid "Upload File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:47
+msgid "Create New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:53
+msgid "New file type"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:64
+#: kallithea/templates/files/files_delete.html:34
+#: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
+msgid "Commit Changes"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:40
+msgid "Search File List"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:45
+msgid "Loading file list..."
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:55
+#: kallithea/templates/summary/summary.html:153
+msgid "Size"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:56
+msgid "Last Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:57
+msgid "Last Modified"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:58
+msgid "Last Committer"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:4
+#, python-format
+msgid "%s Files Delete"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:12
+#: kallithea/templates/files/files_delete.html:30
+msgid "Delete file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:4
+#, python-format
+msgid "%s File Edit"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:21
+msgid "Edit file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
+msgid "Show Annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
+msgid "Source"
+msgstr ""
+
+#: kallithea/templates/files/files_history_box.html:2
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/files/files_source.html:6
+msgid "Diff to Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:7
+msgid "Show at Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:10
+msgid "Show Authors"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:26
+msgid "Show Source"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:41
+msgid "Deleting files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:58
+#, python-format
+msgid "Binary file (%s)"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:73
+msgid "Show as raw."
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:23
+msgid "Go Back"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:24
+msgid "No files at given path"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:9
+#: kallithea/templates/summary/summary.html:138
+#: kallithea/templates/summary/summary.html:139
+msgid "Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers_data.html:9
+msgid "Started following -"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:5
+#, python-format
+msgid "Fork repository %s"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:25
+msgid "Fork name"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:53
+msgid "Default revision for files page, downloads, whoosh, and readme."
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:58
+msgid "Private"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:66
+msgid "Copy permissions"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:69
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:75
+msgid "Update after clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:78
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:85
+msgid "Fork this Repository"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:9
+#: kallithea/templates/summary/summary.html:144
+#: kallithea/templates/summary/summary.html:145
+msgid "Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:14
+msgid "Forked"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:24
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "There are no forks yet"
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
+msgid "RSS journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:34
+msgid "My Repositories"
+msgstr ""
+
+#: kallithea/templates/journal/journal_data.html:42
+msgid "No entries yet"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:10
+msgid "ATOM public journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:11
+msgid "RSS public journal feed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:4
+#: kallithea/templates/pullrequests/pullrequest.html:8
+msgid "New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:26
+#: kallithea/templates/pullrequests/pullrequest_data.html:15
+#: kallithea/templates/pullrequests/pullrequest_show.html:29
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
+msgid "Title"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:28
+msgid "Summarize the changes - or leave empty"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
+msgid "Write a short description on this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:40
+msgid "Changeset flow"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:46
+msgid "Origin repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
+msgid "Destination repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:6
+msgid "No entries"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:14
+msgid "Vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:18
+msgid "From"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:19
+msgid "To"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:28
+#, python-format
+msgid "You voted: %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:30
+msgid "You didn't vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:35
+msgid "(no title)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:37
+#: kallithea/templates/pullrequests/pullrequest_show.html:31
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
+#, fuzzy
+#| msgid "(closed)"
+msgid "Closed"
+msgstr "(Zou)"
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:67
+msgid "Delete Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:68
+msgid "Confirm to delete this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:70
+#, python-format
+msgid "Confirm again to delete this pull request with %s comments"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:6
+#, python-format
+msgid "%s Pull Request %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:10
+#, python-format
+msgid "Pull request %s from %s#%s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
+msgid "Summarize the changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
+msgid "Pull request status calculated from votes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
+msgid "Origin"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
+msgid "on"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
+msgid "Target"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
+msgid ""
+"This is just a range of changesets and doesn't have a target or a real "
+"merge ancestor."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
+msgid "Pull changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
+msgid "Current revision - no change"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
+msgid "Pull Request Content"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:6
+#, python-format
+msgid "%s Pull Requests"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:11
+#, python-format
+msgid "Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:13
+#, python-format
+msgid "Pull Requests to '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
+msgid "Open New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
+#, python-format
+msgid "Show Pull Requests to %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
+#, python-format
+msgid "Show Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:28
+msgid "Hide closed pull requests (only show open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:30
+msgid "Show closed pull requests (in addition to open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
+msgid "Pull Requests Created by Me"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
+msgid "Pull Requests I Participate In"
+msgstr ""
+
+#: kallithea/templates/search/search.html:6
+#, python-format
+msgid "%s Search"
+msgstr ""
+
+#: kallithea/templates/search/search.html:8
+#: kallithea/templates/search/search.html:16
+msgid "Search in All Repositories"
+msgstr ""
+
+#: kallithea/templates/search/search.html:47
+msgid "Search term"
+msgstr ""
+
+#: kallithea/templates/search/search.html:54
+msgid "Search in"
+msgstr ""
+
+#: kallithea/templates/search/search.html:56
+msgid "File contents"
+msgstr ""
+
+#: kallithea/templates/search/search.html:57
+msgid "Commit messages"
+msgstr ""
+
+#: kallithea/templates/search/search.html:58
+msgid "File names"
+msgstr ""
+
+#: kallithea/templates/search/search_commit.html:30
+#: kallithea/templates/search/search_content.html:18
+#: kallithea/templates/search/search_path.html:15
+msgid "Permission denied"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:4
+#, python-format
+msgid "%s Statistics"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:16
+#: kallithea/templates/summary/summary.html:27
+#, python-format
+msgid "%s ATOM feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:17
+#: kallithea/templates/summary/summary.html:28
+#, python-format
+msgid "%s RSS feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:99
+#: kallithea/templates/summary/summary.html:113
+msgid "Enable"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:38
+msgid "Stats gathered: "
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
+msgid "files"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
+msgid "Show more"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:405
+msgid "files changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:408
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:410
+msgid "file changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:411
+msgid "file removed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:5
+#, python-format
+msgid "%s Summary"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:13
+msgid "Fork of"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:18
+msgid "Clone from"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:54
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:63
+msgid "Use ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:65
+#: kallithea/templates/summary/summary.html:73
+msgid "Use SSH"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:71
+msgid "Use Name"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:80
+msgid "Use HTTP"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:92
+msgid "Trending files"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:106
+msgid "Download"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:109
+#, fuzzy
+#| msgid "There are no changesets yet"
+msgid "There are no downloads yet"
+msgstr "Et sinn nach keng Ännerungen do"
+
+#: kallithea/templates/summary/summary.html:111
+msgid "Downloads are disabled for this repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:117
+msgid "Download as zip"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:121
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:123
+msgid "With subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:163
+msgid "Feed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:183
+msgid "Latest Changes"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:185
+msgid "Quick Start"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:212
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:230
+#, python-format
+msgid "Readme file from revision %s:%s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:315
+#, python-format
+msgid "Download %s as %s"
+msgstr ""
--- a/kallithea/i18n/nb_NO/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/nb_NO/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3.99\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-04-30 22:25+0000\n"
 "Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
 "Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
@@ -17,14 +17,14 @@
 "X-Generator: Weblate 3.6.1\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Ingen endringssett enda"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -33,38 +33,38 @@
 msgid "None"
 msgstr "Ingen"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(lukket)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Vis blanktegn"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorer blanktegn"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Øk diff-bindeleddsinformasjon til %(num)s linjer"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "No permission to change pull request status"
 msgid "No permission to change status"
 msgstr "Ingen tilgang til endring av innsendingsforespørselsstatus"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Slettet flettingsforespørsel %s"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "En slik revisjon funnes ikke for denne pakkebrønnen"
 
@@ -77,51 +77,51 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Kan ikke sammenligne pakkebrønner av forskjellige typer"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 #, fuzzy
 msgid "Cannot show empty diff"
 msgstr "Kan ikke vise tom diff"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Kan ikke sammenligne pakkebrønner uten bruk av felles opphav"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Ingen respons"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Ukjent feil"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Forespørselen kunne ikke forstås av tjeneren som følge av feilaktig "
 "syntaks."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Uautorisert tilgang til ressurs"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Du har ikke tilgang til å se denne siden"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Kunne ikke finne ressursen"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -129,14 +129,14 @@
 "Tjeneren støtte på en uventet tilstand som forhindret utøvelse av "
 "forespørsel."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s sendte inn %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -144,12 +144,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Endringsettet var for stort og har blitt avskåret…"
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, fuzzy, python-format
 msgid "%s %s feed"
 msgstr "%s %s kilde"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, fuzzy, python-format
 msgid "Changes on %s repository"
 msgstr "Endringer i %s-pakkebrønn"
@@ -169,94 +169,94 @@
 msgid "%s at %s"
 msgstr "%s den %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Du kan bare slette filer med en revisjon som er en gyldig forgrening"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Slettet filen %s via Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Filen %s ble slettet"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Feil inntraff under innsendelse"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Du kan bare redigere filer med en revisjon som er en gyldig avgrening"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Filen %s ble endret via Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Ingen endringer"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Innsendt til %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Fil lagt til via Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Inget innhold"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Inget filnavn"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Plasseringen må være en relativ sti, og kan ikke inneholde .. i stien"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Nedlastinger avskrudd"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Ukjent revisjon %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Tom pakkebrønn"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Ukjent arkivtype"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Endringssett"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Forgreninger"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Etiketter"
 
@@ -265,11 +265,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "En uventet feil inntraff under forgrening av pakkebrønnen %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Grupper"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -281,7 +281,7 @@
 msgid "Repositories"
 msgstr "Pakkebrønner"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
@@ -289,190 +289,190 @@
 msgid "Branch"
 msgstr "Forgrening"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Lukkede forgreninger"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Etikett"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Bokmerke"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Offentlig loggbok"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Loggbok"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 #, fuzzy
 msgid "Bad captcha"
 msgstr "Feilaktig CAPTCHA"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, fuzzy, python-format
 msgid "You have successfully registered with %s"
 msgstr "Du har registrer deg på %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Passordbekreftelseskode sendt"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Ugyldig passordtilbakestillingssymbol"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Passord oppdatert"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, fuzzy, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Ugyldig analytiker \"%s\" angitt"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (lukket)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Endringssett"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 #, fuzzy
 msgid "Special"
 msgstr "Spesiell"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Likemennsforgreninger"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Bokmerker"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Feil ved opprettelse av ny innsendelsesforespørsel: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Feil inntraff under opprettelse av innsendelsesforespørsel"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Åpnet en ny innsendelsesforespørsel"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Ingen beskrivelse"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Innsendingsforespørsel oppdatert"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Slettet innsendingsforespørsel"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Denne innsendingsforespørselen har allerede blitt flettet inn i %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 "Denne innsendingsforespørselen har blitt lukket, og kan ikke oppdateres."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Merk: Forgreningen %s har et annet hode: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr "Ugyldig søkespørring. Prøv å sette den i sistattegn."
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr "Ugyldig søkespørring. Prøv å sette den i sistattegn."
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr "Tjeneren har ingen søkeindeks."
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Feil inntraff under søkeoperasjon."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Ingen data klar enda"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Statistikk er avskrudd for denne pakkebrønnen"
@@ -485,80 +485,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "feil inntraff under oppdatering av autentiseringsinnstillinger"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Forvalgte innstillinger oppdatert"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Feil inntraff under oppdatering av forvalg"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "For alltid"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "Fem minutter"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "Én time"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "Én dag"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "Én måned"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Livstid"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Feil inntraff under gist-opprettelse"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Slettet gist-en %s"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Uendret"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Oppdaterte gist-innhold"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Oppdaterte gist-data"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, fuzzy, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Feil inntraff under oppdatering av gist-en %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Du kan ikke endre denne brukeren siden den er avgjørende for hele "
@@ -569,7 +569,7 @@
 msgstr "Kontoen din ble oppdatert"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, fuzzy, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Feil inntraff under oppdatering av brukeren %s"
@@ -579,47 +579,47 @@
 msgstr "Feil inntraff under oppdatering av brukerpassord"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, fuzzy, python-format
 msgid "Added email %s to user"
 msgstr "La til e-postadressen %s for bruker"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 #, fuzzy
 msgid "An error occurred during email saving"
 msgstr "Feil inntraff under lagring av e-postadresse"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 #, fuzzy
 msgid "Removed email from user"
 msgstr "Fjernet e-postadresse fra bruker"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API-nøkkel opprettet"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "API-nøkkel tilbakestilt"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API-nøkkel slettet"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "API-nøkkel opprettet"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -697,11 +697,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Tillatt med automatisk kontoaktivering"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Manuell aktivering av ekstern konto"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Automatisk aktivering av ekstern konto"
 
@@ -724,60 +724,60 @@
 msgid "Error occurred during update of permissions"
 msgstr "Feil inntraff under oppdatering av tilganger"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Feil inntraff under opprettelse av pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Opprettet pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Oppdaterte pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Feil inntraff under oppdatering av pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Denne gruppen inneholder %s pakkebrønner og kan ikke slettes"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Denne grunnen inneholder %s undergrupper og kan ikke slettes"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Fjernet pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Feil inntraff under sletting av pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 #, fuzzy
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Kan ikke tilbakekalle egen administratortilgang"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Pakkebrønnsgruppetilganger oppdatert"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "En feil inntraff under tilbakekalling av tilgang"
 
@@ -906,7 +906,7 @@
 msgid "Updated VCS settings"
 msgstr "Oppdaterte VCS-innstillinger"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -973,97 +973,97 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Opprettet brukergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Oppdaterte brukergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 #, fuzzy
 msgid "Successfully deleted user group"
 msgstr "Brukergruppe slettet"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Opprettet brukeren %s"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "Forvalgt bruker kan ikke redigeres"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "La til IP-adressen %s i brukerhvitlisten"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Kunne ikke legge til IP-adresse"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "Fjernet IP-adressen fra brukerhvitlisten"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1094,178 +1094,178 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Forgreningsnavn %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Flettingsforespørsel %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 #, fuzzy
 msgid "[deleted] repository"
 msgstr "[slettet] pakkebrønn"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 #, fuzzy
 msgid "[created] repository"
 msgstr "[opprettet] pakkebrønn"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[opprettet] pakkebrønn som forgrening"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 #, fuzzy
 msgid "[forked] repository"
 msgstr "[forgrenet] pakkebrønn"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 #, fuzzy
 msgid "[updated] repository"
 msgstr "[oppdaterte] pakkebrønn"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 #, fuzzy
 msgid "[downloaded] archive from repository"
 msgstr "[lastet ned] arkiv fra pakkebrønn"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 #, fuzzy
 msgid "[delete] repository"
 msgstr "[slett] pakkebrønn"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[opprettet] bruker"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[oppdaterte] bruker"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[opprettet] brukergruppe"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[oppdaterte] brukergruppe"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 #, fuzzy
 msgid "[commented] on revision in repository"
 msgstr "[kommenterte] en revisjon i pakkebrønn"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[kommenterte] flettingsforespørsel for"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[lukket] flettingsforespørsel for"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[dyttet] til"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 #, fuzzy
 msgid "[committed via Kallithea] into repository"
 msgstr "[innsendt via Kallithea] inn i pakkebrønn"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Ingen filer"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "ny fil"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1273,96 +1273,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d år"
 msgstr[1] "%d år"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d måned"
 msgstr[1] "%d måneder"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d dag"
 msgstr[1] "%d dager"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d time"
 msgstr[1] "%d timer"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d minutt"
 msgstr[1] "%d minutter"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d sekund"
 msgstr[1] "%d sekunder"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "om %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "for %s siden"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "om %s og %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s og %s siden"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "akkurat nå"
 
@@ -1371,134 +1373,134 @@
 msgid "on line %s"
 msgstr "på linje %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "toppnivå"
 
-#: kallithea/model/db.py:1637
+#: kallithea/model/db.py:1634
 msgid "Kallithea Administrator"
 msgstr "Kallithea-administrator"
 
-#: kallithea/model/db.py:1639
+#: kallithea/model/db.py:1636
 #, fuzzy
 msgid "Default user has no access to new repositories"
 msgstr "Forvalgt bruker har ingen tilgang til nye pakkebrønner"
 
-#: kallithea/model/db.py:1640
+#: kallithea/model/db.py:1637
 msgid "Default user has read access to new repositories"
 msgstr ""
 
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1639
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
+msgid "Not reviewed"
+msgstr ""
+
+#: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
 msgid "Not approved"
 msgstr "Ikke godkjent"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Godkjent"
 
@@ -1524,145 +1526,145 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "Lukker"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2359,7 +2361,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3410,7 +3412,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "Lagre innstillinger"
 
@@ -3652,53 +3654,53 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4340,23 +4342,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4365,7 +4367,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4374,8 +4376,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5339,45 +5341,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-10-05 19:28+0000\n"
 "Last-Translator: Thomas De Schampheleire <patrickdepinguin@gmail.com>\n"
 "Language-Team: Flemish <https://hosted.weblate.org/projects/kallithea/"
@@ -18,14 +18,14 @@
 "X-Generator: Weblate 3.9-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Er zijn nog geen changesets"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,37 +34,37 @@
 msgid "None"
 msgstr "Geen"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(gesloten)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Toon witruimtes"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 #, fuzzy
 msgid "Ignore whitespace"
 msgstr "Negeer witruimtes"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Vergroot de diff context tot %(num)s lijnen"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr "Geen toestemming om de status te veranderen"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Deze revisie bestaat niet in deze repository"
 
@@ -77,66 +77,66 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Kan geen repositories van verschillende types vergelijken"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "Kan geen lege diff tonen"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 "Kan geen repositories vergelijken zonder een gemeenschappelijke voorouder "
 "te gebruiken"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Geen antwoord"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Ongekende fout"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "De aanvraag kon niet door de server begrepen worden wegens incorrecte "
 "syntax."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Ongeautoriseerde toegang tot resource"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "U hebt geen permissie om deze pagina te bekijken"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "De resource kon niet gevonden worden"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 "De server kon de aanvraag niet voldoen wegens een onverwachte toestand."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s committeerde op %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -144,12 +144,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "De changeset was te groot en werd afgekort..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s feed"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Veranderingen in repository %s"
@@ -167,92 +167,92 @@
 msgid "%s at %s"
 msgstr "%s op %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Men kan enkel bestanden verwijderen als de revisie een geldige branch is"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Bestand %s verwijderd via Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Bestand %s succesvol verwijderd"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Er trad een fout op tijdens het committeren"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Men kan enkel bestanden wijzigen als de revisie een geldige branch is"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Bestand %s gewijzigd via Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Geen wijzigingen"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Succesvol gecommitteerd naar %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Bestand toegevoegd via Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Geen inhoud"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Geen bestandsnaam"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "De locatie moet een relatief pad zijn en mag geen .. bevatten"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Downloads uitgeschakeld"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Ongekende revisie %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Lege repository"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Ongekende archieftype"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Changesets"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Branches"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Tags"
 
@@ -261,11 +261,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Er is een fout opgetreden tijdens het forken van de repository %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Groepen"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -277,194 +277,194 @@
 msgid "Repositories"
 msgstr "Repositories"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Branch"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Gesloten branches"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Tag"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Bladwijzer"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Publiek logboek"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Logboek"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Incorrecte captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "U bent succesvol geregistreerd bij %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Een paswoordherstel bevestigingscode is verzonden"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Ongeldig paswoordherstel token"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Paswoord succesvol aangepast"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Changeset"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Bijzonder"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -477,80 +477,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -559,7 +559,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -569,44 +569,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr "SSH key succesvol verwijderd"
 
@@ -682,11 +682,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -708,59 +708,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -886,7 +886,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -951,96 +951,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1071,170 +1071,170 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Changeset %s werd niet gevonden"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1242,96 +1242,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1340,133 +1342,133 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1492,145 +1494,146 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
+#: kallithea/model/ssh_key.py:88
+#, fuzzy, python-format
+#| msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "SSH key %r werd niet gevonden"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2319,7 +2322,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3362,7 +3365,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3602,53 +3605,53 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr "Repository paginagrootte"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4276,23 +4279,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4301,7 +4304,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4310,8 +4313,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5271,45 +5274,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/pl/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/pl/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2020-01-27 15:21+0000\n"
 "Last-Translator: robertus <robertuss12@gmail.com>\n"
 "Language-Team: Polish <https://hosted.weblate.org/projects/kallithea/"
@@ -13,19 +13,19 @@
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
-"|| n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n"
+"%100<10 || n%100>=20) ? 1 : 2;\n"
 "X-Generator: Weblate 3.11-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Brak zestawienia zmian"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,38 +34,38 @@
 msgid "None"
 msgstr "Brak"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(zamknięty)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "pokazuj spacje"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignoruj pokazywanie spacji"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 msgid "No permission to change status"
 msgstr "Brak uprawnień do zmiany statusu"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr ""
 "Prośba o skasowanie połączenia gałęzi %s została wykonana prawidłowo"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -78,50 +78,50 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "Brak odpowiedzi"
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr "Brak odpowiedzi"
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr "Nieznany błąd"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Żądanie nie może być rozumiane przez serwer z powodu zniekształconej "
 "składni."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Nieautoryzowany dostęp do zasobów"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Nie masz uprawnień do przeglądania tej strony"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Zasób nie został znaleziony"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -129,14 +129,14 @@
 "Serwer napotkał niespodziewany warunek, który uniemożliwia spełnienie "
 "żądania."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s zakomitowal w %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -144,12 +144,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Lista zmian była zbyt duża i została ucięta..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s zasilać"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Zmiany w %s repozytorium"
@@ -167,93 +167,93 @@
 msgid "%s at %s"
 msgstr "w %s i %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Można tylko usuwać pliki po sprawdzeniu obecnej gałęzi"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Wystąpił błąd w trakcie zatwierdzania"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 #, fuzzy
 msgid "You can only edit files with revision being a valid branch"
 msgstr "Można tylko edytować pliki z rewizji obecnej gałęzi"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Edytowanie %s w Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Bez zmian"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Committ wykonany do %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Dodano %s poprzez Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Brak treści"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Brak nazwy pliku"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Lokalizacja musi być ścieżką względną i nie może zawierać .. ścieżki"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Pobieranie wyłączone"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Nieznana wersja %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Puste repozytorium"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Nieznany typ archiwum"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Różnice"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Gałęzie"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Etykiety"
 
@@ -262,11 +262,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Wystąpił błąd podczas rozgałęzienia %s repozytorium"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Grupy"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -278,196 +278,196 @@
 msgid "Repositories"
 msgstr "Repozytoria"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "gałąź"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Zamknięte Gałęzie"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Tag"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Zakładka"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Dziennik Publiczny"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Dziennik"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Udało Ci się zarejestrować w %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Twój link zresetowania hasła został wysłany"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Nieprawidłowy token resetowania hasła"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Pomyślnie zaktualizowano hasło"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (zamknięty)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Grupy zmian"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Specjalne"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "gałęzie"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Zakładki"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Wystąpił błąd podczas prośby o połączenie gałęzi"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Prośba o wykonanie połączenia gałęzi została wykonana prawidłowo"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "Recenzje wniosków połączenia gałęzi"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Brak opisu"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Połączone gałęzie zaktualizowane"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Nieprawidłowe zapytanie. Spróbuj zacytować je."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Wystąpił błąd podczas operacji wyszukiwania."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Żadne dane nie zostały załadowane"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Statystyki są wyłączone dla tego repozytorium"
@@ -480,80 +480,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "wystąpił błąd podczas uaktualniania ustawień autentykacji"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Domyślne ustawienia zostały pomyślnie zaktualizowane"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "wystąpił błąd podczas aktualizacji wartości domyślnych"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "na zawsze"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 minut"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 godzina"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 dzień"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 miesiąc"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Czas życia"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Wystąpił błąd podczas tworzenia gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Usuń gist %s"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Niemodyfikowany"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej "
@@ -564,7 +564,7 @@
 msgstr "Twoje konto zostało pomyślnie zaktualizowane"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "wystąpił błąd podczas aktualizacji użytkownika %s"
@@ -574,44 +574,44 @@
 msgstr "Wystąpił błąd w trakcie aktualizacji hasła użytkownika"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Dodano e-mail %s do użytkownika"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Wystąpił błąd podczas zapisywania e-maila"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "Usunięto e-mail użytkownikowi"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "Successfully deleted user"
 msgid "SSH key successfully deleted"
@@ -689,11 +689,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Dozwolona z automatyczną aktywacją konta"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Ręczna aktywacja nowego konta"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Automatyczna aktywacja nowego konta"
 
@@ -715,59 +715,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Wystąpił błąd podczas aktualizacji uprawnień"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Wystąpił błąd podczas tworzenia grupy repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Utworzono grupę repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Zaktualizowano grupę repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Wystąpił błąd podczas aktualizacji grupy repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Ta grupa zawiera %s repozytorium i nie może być usunięta"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Ta grupa zawiera %s repozytorium i nie może być usunięta"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Usunięto grupę repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Wystąpił błąd podczas usuwania z repozytorium grupy %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Nie można cofnąć zezwolenia dla admina jako admin"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Aktualizacja uprawnień grup repozytorium"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Wystąpił błąd podczas cofania zezwolenia"
 
@@ -895,7 +895,7 @@
 msgid "Updated VCS settings"
 msgstr "Aktualizacja ustawień VCS"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -962,96 +962,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Zadanie ponownej indeksacji whoosh zostało zaplanowane"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Utworzono grupę użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Wystąpił błąd podczas tworzenia grupy użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Zaktualizowano grupę użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Wystąpił błąd podczas aktualizacji grupy użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Grupa użytkowników została usunięta z powodzeniem"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Wystąpił błąd podczas usuwania grupy użytkowników"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "Grupa docelowa nie może być taka sama"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Aktualizacja uprawnień grupy użytkowników"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Aktualizacja uprawnień"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Wystąpił błąd podczas zapisywania uprawnień"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Utworzono użytkownika %s"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Wystąpił błąd podczas tworzenia użytkownika %s"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Użytkownik został zaktualizowany"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Użytkownik został usunięty"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Wystąpił błąd podczas usuwania użytkownika"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Dodano ip %s do listy dozwolonych adresów użytkownika"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Wystąpił błąd podczas zapisywania adresu IP"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "Usunięto adres ip z listy dozwolonych adresów dla użytkownika"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr "Musisz być zarejestrowanym użytkownikiem, żeby wykonać to działanie"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Musisz być zalogowany, żeby oglądać stronę"
 
@@ -1087,170 +1087,170 @@
 "Lista zmian była zbyt duża i została obcięta, użyj menu porównań żeby "
 "wyświetlić różnice"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Nie wykryto zmian"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Usunięta gałąź: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Utworzony tag: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Nie znaleziono changeset %s"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Pokaż wszystkie zestawienia zmian changesets %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Wyświetl porównanie"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "i"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s więcej"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "rewizja"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "nazwa rozgałęzienia %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, fuzzy, python-format
 msgid "Pull request %s"
 msgstr "Połączonych gałęzi #%s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[usunięte] repozytorium"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[utworzone] repozytorium"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[utworzone] repozytorium jako rozgałęzienie"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[rozgałęzione] repozytorium"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[zaktualizowane] repozytorium"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[pobierz] archiwum z repozytorium"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[skasowane] repozytorium"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[utworzony] użytkownik"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[zaktualizowany] użytkownik"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[utworzona] grupa użytkowników"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[zaktualizowana] grupa użytkowników"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[komentarz] do zmiany w repozytorium"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[komentarz] wniosek o połączenie gałęzi"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[zamknięty] wniosek o połączenie gałęzi"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[wysłane zmiany] w"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[synchronizacja przez Kallithea] z repozytorium"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[pobieranie z zdalnego] do repozytorium"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[pobrano]"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[start następnego] repozytorium"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[zatrzymany po] repozytorium"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " i %s więcej"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Brak plików"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "nowy plik"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "modyfikuj"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "kasuj"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "zmień nazwę"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1261,34 +1261,36 @@
 "zmienione z systemie plików proszę uruchomić aplikację ponownie, aby "
 "ponownie przeskanować repozytoria"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1296,7 +1298,7 @@
 msgstr[1] "%d lata"
 msgstr[2] "%d lat"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1304,7 +1306,7 @@
 msgstr[1] "%d miesięcy"
 msgstr[2] "%d miesięcy"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1312,7 +1314,7 @@
 msgstr[1] "%d dni"
 msgstr[2] "%d dni"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1320,7 +1322,7 @@
 msgstr[1] "%d godziny"
 msgstr[2] "%d godzin"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1328,7 +1330,7 @@
 msgstr[1] "%d minuty"
 msgstr[2] "%d minut"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1336,27 +1338,27 @@
 msgstr[1] "%d sekund"
 msgstr[2] "%d sekund"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "w %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s temu"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "w %s i %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s i %s temu"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "przed chwilą"
 
@@ -1365,144 +1367,144 @@
 msgid "on line %s"
 msgstr "widziany %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Wymieniony]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "najwyższy poziom"
 
-#: kallithea/model/db.py:1637
+#: kallithea/model/db.py:1634
 msgid "Kallithea Administrator"
 msgstr "Administrator Kallithea"
 
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1637
+msgid "Default user has read access to new repositories"
+msgstr "Użytkownik domyślny ma dostęp do odczytu nowych repozytoriów"
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
+msgstr "Użytkownik domyślny ma dostęp do zapisu nowych repozytoriów"
+
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
-msgstr "Użytkownik domyślny ma dostęp do odczytu nowych repozytoriów"
+msgid "Default user has admin access to new repositories"
+msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
-msgstr "Użytkownik domyślny ma dostęp do zapisu nowych repozytoriów"
+msgid "Default user has no access to new repository groups"
+msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1654
 #, fuzzy
 msgid "Only admins can create repository groups"
 msgstr "Tylko admini mogą tworzyć grupy repozytoriów"
 
-#: kallithea/model/db.py:1655
+#: kallithea/model/db.py:1652
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr ""
 "Użytkownicy bez uprawnień administratora mogą tworzyć grupy repozytoriów"
 
-#: kallithea/model/db.py:1657
+#: kallithea/model/db.py:1654
 #, fuzzy
 msgid "Only admins can create user groups"
 msgstr "Tylko admini mogą tworzyć grupy użytkowników"
 
-#: kallithea/model/db.py:1658
+#: kallithea/model/db.py:1655
 #, fuzzy
 msgid "Non-admins can create user groups"
 msgstr ""
 "Użytkownicy bez uprawnień administratora mogą tworzyć grupy użytkowników"
 
+#: kallithea/model/db.py:1657
+msgid "Only admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1658
+msgid "Non-admins can create top level repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1666
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Tylko admini mogą rozgałęziać repozytoria"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr ""
 "Użytkownicy bez uprawnień administratora mogą rozgałęziać repozytoria"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "Rejestracja wyłączona"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr "Rejestracja użytkownika z ręczną aktywacją konta"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr "Rejestracja użytkownika z automatyczną aktywacją konta"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "Brak Korekty"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "Objęty Przeglądem"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 #, fuzzy
 #| msgid "Approved"
 msgid "Not approved"
 msgstr "Niezaakceptowano"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Zaakceptowano"
 
@@ -1528,7 +1530,7 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, fuzzy, python-format
 #| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
 msgid ""
@@ -1536,118 +1538,118 @@
 "%(branch)s"
 msgstr "[komentarz] wniosek o połączenie gałęzi"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Użytkownik %(new_username)s zarejestrował się"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 #, fuzzy
 msgid "Closing"
 msgstr "Zamykanie"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, fuzzy, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 "%(user)s chce żeby przejrzeć nowe gałęzie %(pr_nice_id)s: %(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Create Pull Request"
 msgid "Cannot create empty pull request"
 msgstr "Nie można stworzyć pustego żądania połączenia gałęzi"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 #, fuzzy
 #| msgid "Confirm to delete this pull request"
 msgid "You are not authorized to create the pull request"
 msgstr "Nie masz uprawnień, aby stworzyć żądanie połączenia gałęzi"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "ostatni tip"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset %s not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Nie znaleziono changeset %s"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "nowy użytkownik się zarejestrował"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Nie możesz usunąć tego użytkownika ponieważ jest kluczowy dla całej "
 "aplikacji"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1656,7 +1658,7 @@
 "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
 "zostać usunięty. Zmień właściciela lub usuń te repozytoria: %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1665,7 +1667,7 @@
 "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
 "zostać usunięty. Zmień właściciela lub usuń te repozytoria: %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1674,16 +1676,16 @@
 "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
 "zostać usunięty. Zmień właściciela lub usuń te grupy użytkowników: %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "łącze resetowania hasła"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 #, fuzzy
 msgid "Password reset notification"
 msgstr "Powiadomienie o resetowaniu hasła"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2391,7 +2393,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3499,7 +3501,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 #, fuzzy
 msgid "Save Settings"
 msgstr "Zapisz ustawienia"
@@ -3753,57 +3755,57 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repository Size"
 msgid "Repository page size"
 msgstr "Rozmiar Repozytorium"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Ikony"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Pokazuj w publicznym repo ikonę w repozytoriach"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Pokazuj w prywatnym repo ikonę w repozytoriach"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 #, fuzzy
 msgid "Show public/private icons next to repository names."
 msgstr "Pokazuj  ikonę publiczne/prywatne repo w repozytoriach."
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Tagowanie meta"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4470,26 +4472,26 @@
 msgid "Merge"
 msgstr "połącz"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 #, fuzzy
 msgid "Grafted from:"
 msgstr "Połączono z:"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 #, fuzzy
 msgid "Replaced by:"
 msgstr "Zastąpiono przez:"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 #, fuzzy
 msgid "Preceded by:"
 msgstr "Poprzedzone przez:"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4499,7 +4501,7 @@
 msgstr[1] "%s pliki zostały zmienione"
 msgstr[2] "%s plików zostało zmienionych"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4509,8 +4511,8 @@
 msgstr[1] "%s plików zostało zmienionych z %s inercjami i %s usunięciami"
 msgstr[2] "%s plików zostało zmienionych z %s inercjami i %s usunięciami"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5558,45 +5560,45 @@
 msgid "Stats gathered: "
 msgstr "Statystyki zebrane: "
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "pliki"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Pokaż więcej"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "komunikaty"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "pliki dodane"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "pliki zmienione"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "pliki usunięte"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "pliki usunięte"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "komunikaty"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "plik dodany"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "plik zmieniony"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "plik usunięty"
 
--- a/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2014-02-13 14:34+0000\n"
 "Last-Translator: Marcin Kuźmiński <marcin@python-works.com>\n"
 "Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/"
@@ -16,14 +16,14 @@
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Não há nenhum changeset ainda"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -32,37 +32,37 @@
 msgid "None"
 msgstr "Nenhum"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(fechado)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Mostrar espaços em branco"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorar espaços em branco"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 msgid "No permission to change status"
 msgstr "Vote para estado do pull request"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, fuzzy, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Pull request excluído com sucesso"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -76,51 +76,51 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "revisões"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "A requisição não pôde ser compreendida pelo servidor devido à sintaxe mal "
 "formada."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Acesso não autorizado ao recurso"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Você não tem permissão para ver esta página"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "O recurso não pôde ser encontrado"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -128,14 +128,14 @@
 "O servidor encontrou uma condição inesperada que o impediu de satisfazer "
 "a requisição."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s commitados em %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -143,12 +143,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Conjunto de mudanças era grande demais e foi cortado..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s - feed %s"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Modificações no repositório %s"
@@ -168,92 +168,92 @@
 msgid "%s at %s"
 msgstr "em %s e %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Só é possível editar arquivos quando a revisão é um ramo válido"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Ocorreu um erro ao realizar commit"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 #, fuzzy
 msgid "You can only edit files with revision being a valid branch"
 msgstr "Só é possível editar arquivos quando a revisão é um ramo válido"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Arquivo %s editado via Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Sem modificações"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Commit realizado com sucesso para %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Arquivo adicionado via Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Nenhum conteúdo"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Nenhum nome de arquivo"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "O caminho deve ser relativo e não pode conter .."
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Downloads desabilitados"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Revisão desconhecida %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Repositório vazio"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Tipo de arquivo desconhecido"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Conjuntos de mudanças"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Ramos"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Etiquetas"
 
@@ -262,11 +262,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Ocorreu um erro ao bifurcar o repositório %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -278,203 +278,203 @@
 msgid "Repositories"
 msgstr "Repositórios"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Ramo"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Ramos Fechados"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Diário Público"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Diário"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Você foi registrado no %s com sucesso"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 #, fuzzy
 msgid "A password reset confirmation code has been sent"
 msgstr "Seu link de reinicialização de senha foi enviado"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 #, fuzzy
 msgid "Invalid password reset token"
 msgstr "Link para trocar senha"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Conjunto de Mudanças"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Especial"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Ramos pares"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Marcadores"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 #, fuzzy
 msgid "Error occurred while creating pull request"
 msgstr "Ocorreu um erro durante o envio do pull request"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Novo pull request criado com sucesso"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "Revisores do pull request"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 #, fuzzy
 msgid "No description"
 msgstr "Descrição"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 #, fuzzy
 msgid "Pull request updated"
 msgstr "Pull requests para %s"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Pull request excluído com sucesso"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "Consulta de busca inválida. Tente usar aspas."
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 #, fuzzy
 msgid "An error occurred during search operation."
 msgstr "Ocorreu um erro durante essa operação de busca"
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 #, fuzzy
 msgid "No data ready yet"
 msgstr "Ainda não há dados carregados"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "As estatísticas estão desabillitadas para este repositório"
@@ -487,82 +487,82 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Configurações padrão atualizadas com sucesso"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Ocorreu um erro durnge a atualização dos padrões"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 #, fuzzy
 msgid "Forever"
 msgstr "para sempre"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "cinco minutos"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "uma hora"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "um dia"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "um mês"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Ocorreu um erro durante a criação de um gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist %s excluído"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 #, fuzzy
 msgid "Unmodified"
 msgstr "Última alteração"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Você não pode editar esse usuário pois ele é crucial para toda a aplicação"
@@ -572,7 +572,7 @@
 msgstr "Sua conta foi atualizada com sucesso"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Ocorreu um erro durante a atualização do usuário %s"
@@ -582,44 +582,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Email %s adicionado ao usuário"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Ocorreu um erro durante o salvamento do email"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "Email removido do usuário"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "Successfully deleted user"
 msgid "SSH key successfully deleted"
@@ -697,11 +697,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Permitido com ativação automática de conta"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Ativação manual de conta externa"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Ativação automática de conta externa"
 
@@ -723,59 +723,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Ocorreu um erro durante a atualização das permissões"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Ocorreu um erro durante a criação do grupo de repositórios %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Grupo de repositórios %s criado"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Grupo de repositórios %s atualizado"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Ocorreu um erro durante a atualização do grupo de repositórios %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Esse grupo contém %s repositórios e não pode ser excluído"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Este grupo contém %s subgrupos e não pode ser excluído"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Grupo de repositórios %s excluído"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Ocorreu um erro durante a exclusão do grupo de repositórios %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Você não pode revocar sua própria permissão de administrador"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Permissões atualizadas do Grupo de Repositórios"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Ocorreu um erro durante a revocação das permissões"
 
@@ -903,7 +903,7 @@
 msgid "Updated VCS settings"
 msgstr "Configurações de VCS atualizadas"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -972,96 +972,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Tarefa de reindexação do whoosh agendada"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Grupo de usuários %s criado"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Ocorreu um erro durante a criação do grupo de usuários %s"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Grupo de usuários %s atualizado"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Ocorreu um erro durante a atualização do grupo de usuários %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Grupo de usuários excluído com sucesso"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Ocorreu um erro durante a exclusão do grupo de usuários"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "O grupo destino não pode ser o mesmo"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Permissões do Grupo de Usuários atualizadas"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Permissões atualizadas"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Ocorreu um erro durante o salvamento das permissões"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Usuário %s criado"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Ocorreu um erro durante a criação do usuário %s"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Usuário atualizado com sucesso"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Usuário excluído com sucesso"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Ocorreu um erro ao excluir o usuário"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Ocorreu um erro durante o salvamento do IP"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr "Você precisa ser um usuário registrado para realizar essa ação"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Você precisa estar logado para ver essa página"
 
@@ -1097,172 +1097,172 @@
 "Conjunto de mudanças é grande demais e foi cortado, use o menu de "
 "diferenças para ver as diferenças"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Nenhuma alteração detectada"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Excluído ramo: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Tag criada: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "Conjunto de alterações não encontrado"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Ver todos os conjuntos de mudanças combinados %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 #, fuzzy
 msgid "Compare view"
 msgstr "comparar exibir"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "e"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s mais"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "revisões"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, fuzzy, python-format
 msgid "Fork name %s"
 msgstr "nome da bifurcação %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, fuzzy, python-format
 msgid "Pull request %s"
 msgstr "Pull request #%s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "repositório [excluído]"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "repositório [criado]"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "repositório [criado] como uma bifurcação"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "repositório [bifurcado]"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "repositório [atualizado]"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[baixado] archive do repositório"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[excluir] repositório"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "usuário [criado]"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "usuário [atualizado]"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[criado] grupo de usuários"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[atualizado] grupo de usuários"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[comentado] em revisão no repositório"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[comentado] no pull request para"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[fechado] pull request para"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[realizado push] para"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[commitado via Kallithea] no repositório"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[pulled do remote] no repositório"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[realizado pull] a partir de"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[passou a seguir] o repositório"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[parou de seguir] o repositório"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " e mais %s"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Nenhum arquivo"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "novo arquivo"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "excluir"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "renomear"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1273,96 +1273,98 @@
 "renomeado a partir do sistema de arquivos. Por favor, execute a aplicação "
 "outra vez para varrer novamente por repositórios"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d ano"
 msgstr[1] "%d anos"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d mês"
 msgstr[1] "%d meses"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d dia"
 msgstr[1] "%d dias"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d hora"
 msgstr[1] "%d horas"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d minuto"
 msgstr[1] "%d minutos"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d segundo"
 msgstr[1] "%d segundos"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "em %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s atrás"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "em %s e %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s e %s atrás"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "agora há pouco"
 
@@ -1371,147 +1373,147 @@
 msgid "on line %s"
 msgstr "na linha %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Menção]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "nível superior"
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr "Administrador do Kallithea"
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
-msgstr "Administrador do Kallithea"
-
-#: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "Acesso não autorizado ao recurso"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "Acesso não autorizado ao recurso"
 
+#: kallithea/model/db.py:1639
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1641
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1654
 #, fuzzy
 msgid "Only admins can create repository groups"
 msgstr "Grupo de repositórios %s criado"
 
-#: kallithea/model/db.py:1655
+#: kallithea/model/db.py:1652
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr "Grupo de repositórios %s criado"
 
-#: kallithea/model/db.py:1657
+#: kallithea/model/db.py:1654
 #, fuzzy
 msgid "Only admins can create user groups"
 msgstr "Criar grupos de usuários"
 
-#: kallithea/model/db.py:1658
+#: kallithea/model/db.py:1655
 #, fuzzy
 msgid "Non-admins can create user groups"
 msgstr "Criar grupos de usuários"
 
+#: kallithea/model/db.py:1657
+msgid "Only admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1658
+msgid "Non-admins can create top level repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1666
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Criar repositórios"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "Invalidar o cache para todos os repositórios"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "Registro desatilitado"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 #, fuzzy
 msgid "User registration with manual account activation"
 msgstr "Registro de Usuário com ativação manual de conta"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 #, fuzzy
 msgid "User registration with automatic account activation"
 msgstr "Registro de Usuário com ativação automática de conta"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 #, fuzzy
 msgid "Not reviewed"
 msgstr "Não Revisado"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 #, fuzzy
 msgid "Under review"
 msgstr "Sob Revisão"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 #, fuzzy
 #| msgid "Approved"
 msgid "Not approved"
 msgstr "Aprovado"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Aprovado"
 
@@ -1537,7 +1539,7 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, fuzzy, python-format
 #| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
 msgid ""
@@ -1545,111 +1547,111 @@
 "%(branch)s"
 msgstr "[comentado] no pull request para"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, fuzzy, python-format
 msgid "New user %(new_username)s registered"
 msgstr "O username \"%(new_username)s\" não é válido"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 #, fuzzy
 msgid "Closing"
 msgstr "Usando"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, fuzzy, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 "%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Create Pull Request"
 msgid "Cannot create empty pull request"
 msgstr "Criar Pull Request"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 #, fuzzy
 #| msgid "Confirm to delete this pull request"
 msgid "You are not authorized to create the pull request"
 msgstr "Confirme para excluir este pull request"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "tip mais recente"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Changeset not found"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Conjunto de alterações não encontrado"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "Novo registro de usuário"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 #, fuzzy
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
@@ -1657,7 +1659,7 @@
 "Você não pode remover esse usuário, pois ele é crucial para toda a "
 "aplicação"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1666,7 +1668,7 @@
 "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
 "Troque os donos ou remova esses repositórios. %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1675,7 +1677,7 @@
 "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
 "Troque os donos ou remova esses repositórios. %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1684,16 +1686,16 @@
 "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
 "Troque os donos ou remova esses repositórios. %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "Link para trocar senha"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 #, fuzzy
 msgid "Password reset notification"
 msgstr "Link para trocar senha"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2407,7 +2409,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3531,7 +3533,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 #, fuzzy
 msgid "Save Settings"
 msgstr "Salvar configurações"
@@ -3784,57 +3786,57 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repository Size"
 msgid "Repository page size"
 msgstr "Tamanho do Repositório"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Ícones"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Mostrar ícone de repositório público nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Mostrar ícone de repositório privado nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 #, fuzzy
 msgid "Show public/private icons next to repository names."
 msgstr "Mostrar ícone de repositório público nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Meta-Tagging"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4502,26 +4504,26 @@
 msgid "Merge"
 msgstr "mesclar"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 #, fuzzy
 msgid "Grafted from:"
 msgstr "Criado em"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 #, fuzzy
 msgid "Replaced by:"
 msgstr "criado"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 #, fuzzy
 msgid "Preceded by:"
 msgstr "criado"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4530,7 +4532,7 @@
 msgstr[0] "%s arquivo modificado"
 msgstr[1] "%s arquivos modificados"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4539,8 +4541,8 @@
 msgstr[0] "%s arquivo modificado com %s inserções e %s exclusões"
 msgstr[1] "%s arquivos modificados com %s inserções e %s exclusões"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5582,45 +5584,45 @@
 msgid "Stats gathered: "
 msgstr "Estatísticas coletadas:"
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "arquivos"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Mostrar mais"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "commits"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "arquivos adicionados"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "arquivos alterados"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "arquivos removidos"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "arquivos removidos"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "arquivo adicionado"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "arquivo alterado"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "arquivo removido"
 
--- a/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-12-03 08:05+0000\n"
 "Last-Translator: Private <adamantine.sword@gmail.com>\n"
 "Language-Team: Russian <https://hosted.weblate.org/projects/kallithea/"
@@ -13,19 +13,19 @@
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
-"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
 "X-Generator: Weblate 3.10-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Наборы изменений отсутствуют"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,36 +34,36 @@
 msgid "None"
 msgstr "Ничего"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(закрыто)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Отображать пробелы"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Игнорировать пробелы"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Увеличить контекст до %(num)s строк"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr "Недостаточно привилегий для изменения статуса"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Pull-запрос %s успешно удалён"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Нет такой ревизии в этом репозитории"
 
@@ -76,76 +76,77 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Невозможно сравнивать репозитории различных типов"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "Отсутствуют изменения для отображения"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr "Не найдено предка для слияния"
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr "Найдено несколько предков для сравнения слияния"
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Невозможно сравнивать репозитории без общего предка"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Нет ответа"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Неизвестная ошибка"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr "Запрос не распознан сервером из-за неправильного синтаксиса."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Несанкционированный доступ к ресурсу"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "У вас нет прав для просмотра этой страницы"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Ресурс не найден"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 "Сервер не может выполнить запрос из-за неправильного условия в запросе."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s выполнил коммит в %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Changeset was too big and was cut off..."
 msgstr ""
-"Список изменений оказался слишком большим для отображения и был сокращён..."
-
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+"Список изменений оказался слишком большим для отображения и был "
+"сокращён..."
+
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "Лента новостей %s %s"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Изменения в репозитории %s"
@@ -163,94 +164,95 @@
 msgid "%s at %s"
 msgstr "%s (%s)"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
-msgstr "Вы можете удалять файлы только в ревизии, являющейся корректной веткой"
-
-#: kallithea/controllers/files.py:307
+msgstr ""
+"Вы можете удалять файлы только в ревизии, являющейся корректной веткой"
+
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Файл %s удалён с помощью Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Файл %s удалён"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Во время коммита произошла ошибка"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Вы можете редактировать файлы только в ревизии, связанной с существующей "
 "веткой"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Файл %s отредактирован с помощью Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Без изменений"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Изменения применены в %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Файл добавлен с помощью Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Пусто"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Безымянный"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Расположение должно быть относительным путем, и не должно содержать \".."
 "\" в пути"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Возможность скачивать отключена"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Неизвестная ревизия %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Пустой репозиторий"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Неизвестный тип архива"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Набор изменений"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Ветки"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Метки"
 
@@ -259,11 +261,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Произошла ошибка во время создания форка репозитория %s"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Группы"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -275,195 +277,195 @@
 msgid "Repositories"
 msgstr "Репозитории"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Ветка"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Закрытые ветки"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Тэги"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Закладки"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Публичный журнал"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Журнал"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Неверная капча"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Регистрация в %s прошла успешно"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Код для сброса пароля отправлена"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Неверный код сброса пароля"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Пароль обновлён"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Некорректно задан ревьювер «%s»"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (закрыта)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Изменения"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Специальный"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "Ветви участника"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Закладки"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Ошибка при создании pull-запроса: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Произошла ошибка при создании pull-запроса"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Pull-запрос успешно открыт"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr "Создана новая итерация pull-запросов"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr "В то же время, добавлены следующие ревьюверы: %s"
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr "В то же время, удалены следующие ревьюверы: %s"
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Нет описания"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Pull-запрос обновлён"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Pull-запрос успешно удалён"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr "Ревизия %s не найдена в %s"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "Ошибка: не найдены изменения при отображении pull-запроса от %s."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Этот pull-запрос уже принят на ветку %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "Этот pull-запрос был закрыт и не может быть обновлён."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr "Следующие дополнительные изменения доступны на %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "Нет дополнительных изменений для итерации в этом pull-запросе."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Внимание: Ветка %s имеет ещё одну верхушку: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 msgid "Git pull requests don't support iterating yet."
 msgstr "Pull-запросы git пока не поддерживают итерации."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 "Ошибка: не найдены некоторые изменения при отображении pull-запроса от %s."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr "Невозможно отобразить различия — не найдены ревизии PR."
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr "Недопустимый поисковый запрос. Попробуйте заключить его в кавычки."
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr "Недопустимый поисковый запрос. Попробуйте заключить его в кавычки."
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr "На сервере отсутствует поисковый индекс."
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Произошла ошибка при выполнении этого поиска."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Нет данных"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Статистические данные отключены для этого репозитария"
@@ -476,80 +478,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "произошла ошибка при обновлении настроек авторизации"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Настройки по умолчанию успешно обновлены"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Произошла ошибка при обновлении стандартных настроек"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Не ограничено"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 минут"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 час"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 день"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 месяц"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Срок"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Произошла ошибка во время создания gist-записи"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist-запись %s удалена"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Неизменный"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Содержимое gist-записи обновлено"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Данные gist-записи обновлены"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Произошла ошибка при обновлении gist-записи %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Вы не можете изменить данные этого пользователя, поскольку он важен для "
@@ -560,7 +562,7 @@
 msgstr "Ваша учетная запись успешно обновлена"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Произошла ошибка при обновлении пользователя %s"
@@ -570,44 +572,44 @@
 msgstr "Ошибка при обновлении пароля"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Пользователю добавлен e-mail %s"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Произошла ошибка при сохранении e-mail"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "E-mail пользователя удалён"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API-ключ успешно создан"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "API-ключ успешно сброшен"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API-ключ успешно удалён"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr "Ключ SSH %s успешно добавлен"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr "Ключ SSH успешно удалён"
 
@@ -683,11 +685,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Разрешена, с автоматической активацией учётной записи"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Ручная активация внешней учетной записи"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Автоматическая активация внешней учетной записи"
 
@@ -709,59 +711,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Произошла ошибка во время обновления привилегий"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Произошла ошибка при создании группы репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Создана новая группа репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Группа репозиториев %s обновлена"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Произошла ошибка при обновлении группы репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Данная группа содержит %s репозитариев и не может быть удалена"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Группа содержит в себе %s подгрупп и не может быть удалён"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Группа репозиториев %s удалена"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Произошла ошибка при удалении группы репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Администратор не может отозвать свои привелегии"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Привилегии группы репозиториев обновлены"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Произошла ошибка при отзыве привелегии"
 
@@ -887,7 +889,7 @@
 msgid "Updated VCS settings"
 msgstr "Обновлены настройки VCS"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -956,98 +958,98 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Переиндексация базы Whoosh успешно запланирована"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Создана группа пользователей %s"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Произошла ошибка при создании группы пользователей %s"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Группа пользователей %s обновлена"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Произошла ошибка при обновлении группы пользователей %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Группа пользователей успешно удалена"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Произошла ошибка при удалении группы пользователей"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "Целевая группа не может быть такой же"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Привилегии группы пользователей обновлены"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Обновлены привилегии"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Произошла ошибка при сохранении привилегий"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Пользователь %s создан"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Произошла ошибка при создании пользователя %s"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Пользователь успешно обновлён"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Пользователь успешно удалён"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Произошла ошибка при удалении пользователя"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "Нельзя редактировать пользователя по умолчанию"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Добавлен IP %s в белый список пользователя"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Произошла ошибка при сохранении IP"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "Удален IP %s из белого списка пользователя"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 "Вы должны быть зарегистрированным пользователем, чтобы выполнить это "
 "действие"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Страница доступна только авторизованным пользователям"
 
@@ -1080,170 +1082,170 @@
 "Набор изменения оказался слишком большими и был урезан, используйте меню "
 "сравнения для показа результата сравнения"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Изменений не обнаружено"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Удалена ветка: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Создан тег: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Набор изменений %s не найден"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Показать отличия вместе %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Сравнить вид"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "и"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "на %s больше"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "версии"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Имя форка %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull-запрос %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[удален] репозиторий"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[создан] репозиторий"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[создан] репозиторий в качестве форка"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[создан форк] репозитория"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[обновлён] репозиторий"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[загружен] архив из репозитория"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[удален] репозиторий"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[создан] пользователь"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[обновлён] пользователь"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[создана] группа пользователей"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[обновлена] группа пользователей"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[комментарий] к ревизии в репозитории"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[прокомментировано] в pull-запросе для"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[закрыт] pull-запрос для"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[отправлено] в"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[внесены изменения с помощью Kallithea] в репозитории"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[внесены изменения из удалённого репозитория] в репозиторий"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[внесены изменения] из"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[подписка] на репозиторий"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[отписка] от репозитория"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " и на %s больше"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Нет файлов"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "новый файл"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr "изменён"
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr "удалён"
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "переименован"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1254,35 +1256,42 @@
 "переименован из файловой системы. Пожалуйста, перезапустите приложение "
 "для сканирования репозиториев"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr "Отсутствует ключ SSH"
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr "Некорректный ключ SSH — должен присутствовать тип ключа и код base64"
-
 #: kallithea/lib/ssh.py:79
+#, fuzzy
+#| msgid ""
+#| "Incorrect SSH key - it must have both a key type and a base64 part"
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+"Некорректный ключ SSH — должен присутствовать тип ключа и код base64"
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
-msgstr "Некорректный ключ SSH — он должен начинаться с 'ssh-(rsa|dss|ed25519)'"
-
-#: kallithea/lib/ssh.py:82
+msgstr ""
+"Некорректный ключ SSH — он должен начинаться с 'ssh-(rsa|dss|ed25519)'"
+
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 "Некорректный ключ SSH — присутствуют некорректные символы в коде base64 %r"
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr "Некорректный ключ SSH — ошибка декодирования кода base64 %r"
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr "Некорректный ключ SSH — код base64 соответствует не %r, а %r"
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1290,7 +1299,7 @@
 msgstr[1] "%d года"
 msgstr[2] "%d лет"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1298,7 +1307,7 @@
 msgstr[1] "%d месяца"
 msgstr[2] "%d месяцев"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1306,7 +1315,7 @@
 msgstr[1] "%d дня"
 msgstr[2] "%d дней"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1314,7 +1323,7 @@
 msgstr[1] "%d часа"
 msgstr[2] "%d часов"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1322,7 +1331,7 @@
 msgstr[1] "%d минуты"
 msgstr[2] "%d минут"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1330,27 +1339,27 @@
 msgstr[1] "%d секунды"
 msgstr[2] "%d секунд"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "в %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s назад"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "в %s и %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s и %s назад"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "только что"
 
@@ -1359,151 +1368,155 @@
 msgid "on line %s"
 msgstr "на строке %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Упоминание]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "верхний уровень"
 
-#: kallithea/model/db.py:1637
+#: kallithea/model/db.py:1634
 msgid "Kallithea Administrator"
 msgstr "Администратор Kallithea"
 
-#: kallithea/model/db.py:1639
+#: kallithea/model/db.py:1636
 msgid "Default user has no access to new repositories"
 msgstr ""
 "Неавторизованные пользователи не имеют прав доступа к новым репозиториям"
 
-#: kallithea/model/db.py:1640
+#: kallithea/model/db.py:1637
 msgid "Default user has read access to new repositories"
 msgstr "Неавторизованные пользователи имеют право чтения новых репозиториев"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 msgid "Default user has write access to new repositories"
 msgstr ""
 "Неавторизованные пользователи имеют право записи в новые репозитории"
 
-#: kallithea/model/db.py:1642
+#: kallithea/model/db.py:1639
 msgid "Default user has admin access to new repositories"
 msgstr ""
-"Неавторизованные пользователи имеют права администратора к новым репозиториям"
-
-#: kallithea/model/db.py:1644
+"Неавторизованные пользователи имеют права администратора к новым "
+"репозиториям"
+
+#: kallithea/model/db.py:1641
 msgid "Default user has no access to new repository groups"
 msgstr ""
 "Неавторизованные пользователи не имеют прав доступа к новым группам "
 "репозиториев"
 
-#: kallithea/model/db.py:1645
+#: kallithea/model/db.py:1642
 msgid "Default user has read access to new repository groups"
 msgstr ""
-"Неавторизованные пользователи имеют право чтения в новых группах репозиториев"
-
-#: kallithea/model/db.py:1646
+"Неавторизованные пользователи имеют право чтения в новых группах "
+"репозиториев"
+
+#: kallithea/model/db.py:1643
 msgid "Default user has write access to new repository groups"
 msgstr ""
-"Неавторизованные пользователи имеют право записи в новых группах репозиториев"
-
-#: kallithea/model/db.py:1647
+"Неавторизованные пользователи имеют право записи в новых группах "
+"репозиториев"
+
+#: kallithea/model/db.py:1644
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 "Неавторизованные пользователи имеют права администратора к новым групппам "
 "репозиториев"
 
-#: kallithea/model/db.py:1649
+#: kallithea/model/db.py:1646
 msgid "Default user has no access to new user groups"
 msgstr ""
 "Неавторизованные пользователи не имеют прав доступа к новым группам "
 "пользователей"
 
-#: kallithea/model/db.py:1650
+#: kallithea/model/db.py:1647
 msgid "Default user has read access to new user groups"
 msgstr ""
 "Неавторизованные пользователи имеют право чтения в новых группах "
 "пользователей"
 
-#: kallithea/model/db.py:1651
+#: kallithea/model/db.py:1648
 msgid "Default user has write access to new user groups"
 msgstr ""
 "Неавторизованные пользователи имеют право записи в новых группах "
 "пользователей"
 
-#: kallithea/model/db.py:1652
+#: kallithea/model/db.py:1649
 msgid "Default user has admin access to new user groups"
 msgstr ""
 "Неавторизованные пользователи имеют права администратора к новым групппам "
 "пользователей"
 
-#: kallithea/model/db.py:1654
+#: kallithea/model/db.py:1651
 msgid "Only admins can create repository groups"
 msgstr "Только администраторы могут создавать группы репозиториев"
 
-#: kallithea/model/db.py:1655
+#: kallithea/model/db.py:1652
 msgid "Non-admins can create repository groups"
 msgstr "Группы репозиториев могут создаваться любыми пользователями"
 
-#: kallithea/model/db.py:1657
+#: kallithea/model/db.py:1654
 msgid "Only admins can create user groups"
 msgstr "Группы пользователей могут создаваться только администраторами"
 
-#: kallithea/model/db.py:1658
+#: kallithea/model/db.py:1655
 msgid "Non-admins can create user groups"
 msgstr "Группы пользователей могут создаваться любыми пользователями"
 
-#: kallithea/model/db.py:1660
+#: kallithea/model/db.py:1657
 msgid "Only admins can create top level repositories"
 msgstr "Только администраторы могут создавать репозитории верхнего уровня"
 
-#: kallithea/model/db.py:1661
+#: kallithea/model/db.py:1658
 msgid "Non-admins can create top level repositories"
 msgstr "Любой пользователь может создавать репозитории верхнего уровня"
 
-#: kallithea/model/db.py:1663
+#: kallithea/model/db.py:1660
 msgid ""
 "Repository creation enabled with write permission to a repository group"
-msgstr "Создание репозиториев доступно с правом на запись в группу репозиториев"
-
-#: kallithea/model/db.py:1664
+msgstr ""
+"Создание репозиториев доступно с правом на запись в группу репозиториев"
+
+#: kallithea/model/db.py:1661
 msgid ""
 "Repository creation disabled with write permission to a repository group"
 msgstr ""
 "Создание репозиториев недоступно с правом на запись в группу репозиториев"
 
-#: kallithea/model/db.py:1666
+#: kallithea/model/db.py:1663
 msgid "Only admins can fork repositories"
 msgstr "Форки репозиториев могут создаваться только администраторами"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 msgid "Non-admins can fork repositories"
 msgstr "Форки репозиториев могут создаваться любыми пользователями"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr "Регистрация отключена"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr "Регистрация пользователя с ручной активацией учётной записи"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr "Регистрация пользователя с автоматической активацией"
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "Не проверено"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "На проверке"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 msgid "Not approved"
 msgstr "Не одобрено"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "Одобрено"
 
@@ -1529,7 +1542,7 @@
 msgid "Name must not contain only digits"
 msgstr "Имя не может состоять только из цифр"
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
@@ -1538,12 +1551,12 @@
 "[Комментарий] к набору изменений %(short_id)s «%(message_short)s» "
 "репозитория %(repo_name)s в %(branch)s"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Новый пользователь \"%(new_username)s\" зарегистрирован"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
@@ -1552,104 +1565,106 @@
 "[Ревью] к PR %(pr_nice_id)s «%(pr_title_short)s» из %(pr_source_branch)s "
 "репозитория %(repo_name)s от %(pr_owner_username)s"
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
-"[Комментарий] к PR %(pr_nice_id)s «%(pr_title_short)s» из %(pr_source_branch)"
-"s репозитория %(repo_name)s от %(pr_owner_username)s"
-
-#: kallithea/model/notification.py:183
+"[Комментарий] к PR %(pr_nice_id)s «%(pr_title_short)s» из "
+"%(pr_source_branch)s репозитория %(repo_name)s от %(pr_owner_username)s"
+
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "Закрыт"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 "%(user)s просит вас рассмотреть pull-запрос %(pr_nice_id)s: %(pr_title)s"
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr "Невозможно создать пустой pull-запрос"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
-"Невозможно создать pull-запрос — обнаружено перекрёстное слияние. Попробуйте "
-"слить более позднюю ревизию %s с %s"
-
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+"Невозможно создать pull-запрос — обнаружено перекрёстное слияние. "
+"Попробуйте слить более позднюю ревизию %s с %s"
+
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr "Недостаточно привилегий для создания pull-запроса"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr "Отсутствующие ревизии относительно предыдущей итерации:"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "Новые наборы изменений в %s %s относительно предыдущей итерации:"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr "Предок не изменился — разница с момента последней итерации:"
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
-msgstr "Эта итерация основана на другой ревизии %s, простой diff невозможен."
-
-#: kallithea/model/pull_request.py:362
+msgstr ""
+"Эта итерация основана на другой ревизии %s, простой diff невозможен."
+
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr "Нет изменений на %s %s относительно предыдущей итерации."
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr "Закрыто. Следующая итерация: %s."
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "последняя версия"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr "Ошибка ключа SSH %r: %s"
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr "Ключ SSH %s уже используется пользователем %s"
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
+#: kallithea/model/ssh_key.py:88
+#, fuzzy, python-format
+#| msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Ключ SSH %r не найден"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr "Регистрация нового пользователя"
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
-"Вы не можете удалить этого пользователя, поскольку это критично для работы "
-"всего приложения"
-
-#: kallithea/model/user.py:255
+"Вы не можете удалить этого пользователя, поскольку это критично для "
+"работы всего приложения"
+
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1658,7 +1673,7 @@
 "Пользователь \"%s\" всё ещё является владельцем %s репозиториев и поэтому "
 "не может быть удалён. Смените владельца или удалите эти репозитории: %s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1668,7 +1683,7 @@
 "поэтому не может быть удалён. Смените владельца или удалите данные "
 "группы: %s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1678,15 +1693,15 @@
 "поэтому не может быть удалён. Смените владельца или удалите данные "
 "группы: %s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr "Ссылка сброса пароля"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr "Уведомление о сбросе пароля"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -1712,9 +1727,9 @@
 "Username may only contain alphanumeric characters underscores, periods or "
 "dashes and must begin with an alphanumeric character or underscore"
 msgstr ""
-"Имя пользователя может содержать только буквы, цифры, символы подчеркивания, "
-"точки и тире, а также должно начинаться с буквы, цифры или с символа "
-"подчеркивания"
+"Имя пользователя может содержать только буквы, цифры, символы "
+"подчеркивания, точки и тире, а также должно начинаться с буквы, цифры или "
+"с символа подчеркивания"
 
 #: kallithea/model/validators.py:103
 msgid "The input is not valid"
@@ -1801,8 +1816,8 @@
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
-"Недопустимый URL репозитория. Требуется корректный http, https, ssh, svn+"
-"http или svn+https URL"
+"Недопустимый URL репозитория. Требуется корректный http, https, ssh, svn"
+"+http или svn+https URL"
 
 #: kallithea/model/validators.py:430
 msgid "Fork has to be the same type as parent"
@@ -2039,8 +2054,8 @@
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr ""
-"Ссылка для сброса пароля была отправлена на соответствующий e-mail, если он "
-"был зарегистрирован в системе."
+"Ссылка для сброса пароля была отправлена на соответствующий e-mail, если "
+"он был зарегистрирован в системе."
 
 #: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
@@ -2052,8 +2067,8 @@
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
-"Обратите внимание, что вы должны оставаться в пределах этой сессии браузера, "
-"поскольку в ней был запрошен сброс пароля."
+"Обратите внимание, что вы должны оставаться в пределах этой сессии "
+"браузера, поскольку в ней был запрошен сброс пароля."
 
 #: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
@@ -2113,8 +2128,8 @@
 #: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
-"Зарегистрированные аккаунты готовы к использованию и не требуют дальнейших "
-"действий."
+"Зарегистрированные аккаунты готовы к использованию и не требуют "
+"дальнейших действий."
 
 #: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
@@ -2282,8 +2297,8 @@
 "Gist was updated since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
-"Gist был изменён с момента начала редактирования. Скопируйте свои правки и "
-"нажмите %(here)s для загрузки новой версии."
+"Gist был изменён с момента начала редактирования. Скопируйте свои правки "
+"и нажмите %(here)s для загрузки новой версии."
 
 #: kallithea/templates/admin/gists/edit.html:36
 msgid "here"
@@ -2401,7 +2416,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -2764,8 +2779,8 @@
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
-"Разрешить доступ к Kallithea без авторизации. Анонимные пользователи будут "
-"использовать права доступа пользователя %s."
+"Разрешить доступ к Kallithea без авторизации. Анонимные пользователи "
+"будут использовать права доступа пользователя %s."
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
@@ -2823,8 +2838,8 @@
 "be lost"
 msgstr ""
 "Выбранные привилегии будут установлены по умолчанию для каждой группы "
-"пользователей. Учтите, что ранее установленные привилегии по умолчанию для "
-"групп пользователей будут сброшены"
+"пользователей. Учтите, что ранее установленные привилегии по умолчанию "
+"для групп пользователей будут сброшены"
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
@@ -2864,7 +2879,8 @@
 "mean nothing."
 msgstr ""
 "С этой опцией, право записи в группу репозиториев позволяет создавать "
-"репозитории в этой группе. Без неё, право записи в группу не имеет действия."
+"репозитории в этой группе. Без неё, право записи в группу не имеет "
+"действия."
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
@@ -2873,7 +2889,8 @@
 #: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
-"Включите для возможности создавать группы пользователей любым пользователям."
+"Включите для возможности создавать группы пользователей любым "
+"пользователям."
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
@@ -3067,8 +3084,8 @@
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
-"Установить или отозвать права всех дочерних элементов этой группы, включая "
-"публичные репозитории и другие группы, если они выбраны."
+"Установить или отозвать права всех дочерних элементов этой группы, "
+"включая публичные репозитории и другие группы, если они выбраны."
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
 msgid "Remove this group"
@@ -3132,8 +3149,8 @@
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
 msgstr ""
-"Ревизия по умолчанию для страницы файлов, загрузки, полнотекстовый поисковый "
-"индекс и генерация readme"
+"Ревизия по умолчанию для страницы файлов, загрузки, полнотекстовый "
+"поисковый индекс и генерация readme"
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
 #, python-format
@@ -3361,8 +3378,8 @@
 "группу, URL репозитория изменяется.\n"
 "                               Использование постоянного URL гарантирует, "
 "что данный репозиторий всегда будет доступен по этому URL.\n"
-"                               Это может быть полезно в CI-системах, или в "
-"любом другом случае, требующем встраивания URL в код ПО."
+"                               Это может быть полезно в CI-системах, или "
+"в любом другом случае, требующем встраивания URL в код ПО."
 
 #: kallithea/templates/admin/repos/repo_edit_settings.html:21
 msgid "Remote repository"
@@ -3509,7 +3526,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "Сохранить настройки"
 
@@ -3548,7 +3565,8 @@
 "related to repositories that no longer exist in the filesystem."
 msgstr ""
 "Отметьте для удаления всех комментариев, pull-запросов и других записей, "
-"связанных с репозиториями, которые больше не существуют в файловой системе."
+"связанных с репозиториями, которые больше не существуют в файловой "
+"системе."
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
@@ -3582,9 +3600,9 @@
 "not seem to come from Kallithea. WARNING: This operation will destroy any "
 "custom git hooks you may have deployed by hand!"
 msgstr ""
-"Перезаписывает все существующие хуки при установке хуков Git, даже если они "
-"не поставляются с Kallithea. ПРЕДУПРЕЖДЕНИЕ: это действие уничтожит любые "
-"Git хуки, которые могли быть созданы вручную!"
+"Перезаписывает все существующие хуки при установке хуков Git, даже если "
+"они не поставляются с Kallithea. ПРЕДУПРЕЖДЕНИЕ: это действие уничтожит "
+"любые Git хуки, которые могли быть созданы вручную!"
 
 #: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
@@ -3687,8 +3705,8 @@
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
-"Путь к репозиториям в файловой системе. После изменения значения требуется "
-"перезапуск и пересканирование папки с репозиториями."
+"Путь к репозиториям в файловой системе. После изменения значения "
+"требуется перезапуск и пересканирование папки с репозиториями."
 
 #: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
@@ -3736,14 +3754,14 @@
 "использовать следующие переменные:\n"
 "                                                        {scheme}    "
 "используемый протокол, 'http' или 'https',\n"
-"                                                        {email}     e-mail "
-"пользователя,\n"
-"                                                        {md5email}  хэш md5 "
-"адреса почты пользователя (как на gravatar.com),\n"
+"                                                        {email}     e-"
+"mail пользователя,\n"
+"                                                        {md5email}  хэш "
+"md5 адреса почты пользователя (как на gravatar.com),\n"
 "                                                        {size}      "
 "ожидаемый размер изображения,\n"
-"                                                        {netloc}    сетевой "
-"путь/адрес хоста сервера Kallithea"
+"                                                        {netloc}    "
+"сетевой путь/адрес хоста сервера Kallithea"
 
 #: kallithea/templates/admin/settings/settings_visual.html:40
 msgid "HTTP Clone URL"
@@ -3771,7 +3789,8 @@
 "hostname\n"
 "                                                    "
 msgstr ""
-"Схема URL для клонирования, например: '{scheme}://{user}@{netloc}/{repo}'.\n"
+"Схема URL для клонирования, например: '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
 "                                                    Доступны следующие "
 "переменные:\n"
 "                                                    {scheme} используемый "
@@ -3782,8 +3801,8 @@
 "адрес хоста сервера Kallithea,\n"
 "                                                    {repo}   полное имя "
 "репозитория,\n"
-"                                                    {repoid} ID репозитория, "
-"может применяться для клонирования по идентификатору,\n"
+"                                                    {repoid} ID "
+"репозитория, может применяться для клонирования по идентификатору,\n"
 "                                                    {system_user}  имя "
 "пользователя Kallithea в системе,\n"
 "                                                    {hostname}  имя хоста "
@@ -3799,53 +3818,54 @@
 "Schema for constructing SSH clone URL, eg. 'ssh://{system_user}"
 "@{hostname}/{repo}'."
 msgstr ""
-"Схема URL для клонирования по SSH, например: "
-"'ssh://{system_user}@{hostname}/{repo}'."
-
-#: kallithea/templates/admin/settings/settings_visual.html:65
+"Схема URL для клонирования по SSH, например: 'ssh://{system_user}"
+"@{hostname}/{repo}'."
+
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr "Размер страницы репозитория"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
-"Количество элементов на странице репозитория до появления нумерации страниц."
-
-#: kallithea/templates/admin/settings/settings_visual.html:73
+"Количество элементов на странице репозитория до появления нумерации "
+"страниц."
+
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr "Размер страницы администратора"
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
-"Количество элементов в сетке страницы администратора до появления нумерации "
-"страниц."
-
-#: kallithea/templates/admin/settings/settings_visual.html:81
+"Количество элементов в сетке страницы администратора до появления "
+"нумерации страниц."
+
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Иконки"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Показывать иконки публичных репозиториев"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Показывать иконки приватных репозиториев"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr "Показывать иконки публичных репозиториев."
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr "Метатегирование"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
@@ -3853,7 +3873,7 @@
 "Анализирует мета-теги в поле описания репозитория и отображает их в виде "
 "цветных тегов."
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr "Стилизовать обнаруженные мета-теги:"
 
@@ -4191,7 +4211,8 @@
 #: kallithea/templates/base/default_perms_box.html:35
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
-"Выберите, чтобы разрешить данному пользователю создавать форки репозиториев"
+"Выберите, чтобы разрешить данному пользователю создавать форки "
+"репозиториев"
 
 #: kallithea/templates/base/perms_summary.html:13
 #: kallithea/templates/changelog/changelog.html:41
@@ -4292,7 +4313,8 @@
 
 #: kallithea/templates/base/root.html:46
 msgid "Type name of user or member to grant permission"
-msgstr "Введите имя пользователя или члена группы для предоставления доступа"
+msgstr ""
+"Введите имя пользователя или члена группы для предоставления доступа"
 
 #: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
@@ -4485,23 +4507,23 @@
 msgid "Merge"
 msgstr "Слить"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr "Перенесено из:"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr "Трансплантировано из:"
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr "Заменено:"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr "Предшествует:"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4511,7 +4533,7 @@
 msgstr[1] "%s файлов изменено"
 msgstr[2] "%s файла изменено"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4521,8 +4543,8 @@
 msgstr[1] "%s файла изменёно: %s добавления, %s удаления"
 msgstr[2] "%s файлов изменёно: %s добавлений, %s удалений"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -4841,15 +4863,16 @@
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
-"В случае, если перейти по ссылке не удаётся, введите в форме сброса пароля "
-"следующий код"
+"В случае, если перейти по ссылке не удаётся, введите в форме сброса "
+"пароля следующий код"
 
 #: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
-"Если вы не запрашивали сброс пароля, то просто проигнорируйте это сообщение."
+"Если вы не запрашивали сброс пароля, то просто проигнорируйте это "
+"сообщение."
 
 #: kallithea/templates/email_templates/pull_request.html:4
 #, python-format
@@ -5285,7 +5308,8 @@
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
 #, python-format
 msgid "Confirm again to delete this pull request with %s comments"
-msgstr "Ещё раз подтвердите удаление pull-запроса со всеми (%s) комментариями"
+msgstr ""
+"Ещё раз подтвердите удаление pull-запроса со всеми (%s) комментариями"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:6
 #, python-format
@@ -5506,45 +5530,45 @@
 msgid "Stats gathered: "
 msgstr "Полученная статистика: "
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "файлы"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Показать еще"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "commit'ы"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "файлы добавлены"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "файлы изменены"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "файлы удалены"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "файлы удалены"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "файл удалён"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "файл изменён"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "файл удалён"
 
--- a/kallithea/i18n/sk/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/sk/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2015-04-01 12:59+0200\n"
 "Last-Translator: Andrej Shadura <andrew@shadura.me>\n"
 "Language-Team: Slovak <https://hosted.weblate.org/projects/kallithea/"
@@ -18,14 +18,14 @@
 "Generated-By: Babel 1.3\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Zatiaľ nie sú žiadne zmeny"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,37 +34,37 @@
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(zatvorené)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Ukázať medzery"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorovať medzery"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 msgid "No permission to change status"
 msgstr "Zmeny"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, fuzzy, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Úspešne zmazaný súbor %s"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Taká revízia neexistuje"
 
@@ -78,62 +78,62 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "Neznáma revízia %s"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Nemáte oprávnenie na zobrazenie tejto stránky"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -141,12 +141,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Zmeny na repozitáre %s"
@@ -166,90 +166,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Zmazaný súbor %s cez Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Úspešne zmazaný súbor %s"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Došlo k chybe pri ukladaní"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Žiadne zmeny"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Pridaný súbor cez Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Žiadny obsah"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Sťahovanie vypnuté"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Neznáma revízia %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Prázdny repozitár"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Zmeny"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Vetvy"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Tagy"
 
@@ -258,11 +258,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Skupiny"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -274,195 +274,195 @@
 msgid "Repositories"
 msgstr "Repozitáre"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Vetva"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Záložka"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 #, fuzzy
 msgid "Bad captcha"
 msgstr "zlá captcha"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Úspešne aktualizované heslo"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (zatvorené)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Záložky"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Došlo k chybe počas vyhľadávania."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -475,80 +475,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 minút"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 hodina"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 deň"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 mesiac"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Došlo k chybe pri vytváraní gist"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Došlo k chybe pri aktualizácii gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -557,7 +557,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -567,44 +567,44 @@
 msgstr "Došlo k chybe pri aktualizácii hesla užívateľa"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Došlo k chybe pri ukladaní e-mailovej adresy"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "Successfully deleted file %s"
 msgid "SSH key successfully deleted"
@@ -682,11 +682,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -708,59 +708,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -887,7 +887,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -953,96 +953,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1073,171 +1073,171 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "Zmeny"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1245,34 +1245,36 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1280,7 +1282,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1288,7 +1290,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1296,7 +1298,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1304,7 +1306,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1312,7 +1314,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1320,27 +1322,27 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1349,135 +1351,135 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1666
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Repozitáre"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "Repozitáre"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1503,146 +1505,146 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Set changeset status"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "Zmeny"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2334,7 +2336,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3395,7 +3397,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3636,55 +3638,55 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "Repositories"
 msgid "Repository page size"
 msgstr "Repozitáre"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4317,23 +4319,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4343,7 +4345,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4353,8 +4355,8 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5342,45 +5344,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/tr/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/tr/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.4.99\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-11-05 08:03+0000\n"
 "Last-Translator: Hüseyin Tunç <huseyin.tunc@bulutfon.com>\n"
 "Language-Team: Turkish <https://hosted.weblate.org/projects/kallithea/"
@@ -21,14 +21,14 @@
 "Generated-By: Babel 2.6.0\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -37,36 +37,36 @@
 msgid "None"
 msgstr "Yok"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(kapalı)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Boşlukları göster"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -79,61 +79,61 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -141,12 +141,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
@@ -164,90 +164,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr ""
 
+#: kallithea/controllers/files.py:502
+#, python-format
+msgid "Unknown revision %s"
+msgstr ""
+
 #: kallithea/controllers/files.py:504
-#, python-format
-msgid "Unknown revision %s"
+msgid "Empty repository"
 msgstr ""
 
 #: kallithea/controllers/files.py:506
-msgid "Empty repository"
-msgstr ""
-
-#: kallithea/controllers/files.py:508
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr ""
 
@@ -256,11 +256,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -272,194 +272,194 @@
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr ""
@@ -472,80 +472,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -554,7 +554,7 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -564,44 +564,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr ""
 
@@ -677,11 +677,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -703,59 +703,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -881,7 +881,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -946,96 +946,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr ""
 
@@ -1066,170 +1066,170 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1237,96 +1237,98 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr ""
 
@@ -1335,133 +1337,133 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1487,145 +1489,145 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2314,7 +2316,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3357,7 +3359,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr ""
 
@@ -3597,53 +3599,53 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4271,23 +4273,23 @@
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4296,7 +4298,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4305,8 +4307,8 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5266,45 +5268,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:403
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
 #: kallithea/templates/summary/statistics.html:405
-msgid "commits"
+msgid "files changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
+msgid "files removed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
+msgid "file changed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
--- a/kallithea/i18n/uk/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/uk/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3.2\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-11-13 10:04+0000\n"
 "Last-Translator: Oleksandr Shtalinberg <o.shtalinberg@gmail.com>\n"
 "Language-Team: Ukrainian <https://hosted.weblate.org/projects/kallithea/"
@@ -18,14 +18,14 @@
 "X-Generator: Weblate 3.10-dev\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "Наборів змін немає"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,36 +34,36 @@
 msgid "None"
 msgstr "Нічого"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(закрито)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "Відображати пробіли"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ігнорувати пробіли"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Збільшити відмінність контексту для %(num)s рядків"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 msgid "No permission to change status"
 msgstr "У вас немає дозволу змінювати статус"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "Успішно вилучено pull request %s"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "Така редакція не існує для цього репозиторію"
 
@@ -76,62 +76,62 @@
 msgid "Cannot compare repositories of different types"
 msgstr "Не вдається порівняти репозиторії різних типів"
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr "Не вдалося відобразити пусті відмінності"
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr "Не знайдено предка для злиття відмінностей"
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr "Множинні злиття предків знайдено для злиття порівняти"
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Не вдається порівняти репозиторії без використання спільного предка"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Немає відповіді"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Невідома помилка"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr "Запит не може бути зрозумілий сервером через синтаксичні помилки."
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Несанкціонований доступ до ресурсів"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Ви не маєте дозволу на перегляд цієї сторінки"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Ресурс не може бути знайдений"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 "На сервері виявлено неочікувану умову, яка перешкоджала виконанню запиту."
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s зафіксовано на %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -139,12 +139,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "Changeset був занадто великий і був відрізаний..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s канал"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Зміни в репозиторії  %s"
@@ -162,91 +162,91 @@
 msgid "%s at %s"
 msgstr "%s у  %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Видаляти файли можна лише з ревізії припустимого бренчу"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Видалений файл %s через Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Успішно видалений файл %s"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "Під час фіксації сталася помилка"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr "Редагувати файли можна лише з ревізії, що належить валідному бренчу"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Відредагований файл %s через Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "Нема змін"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Успішно зафіксовано в  %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "Доданий файл через Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "Немає вмісту"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "Не вказано назви файлу"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Розташування має бути відносним шляхом і не повинен містити .. в шляху"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "Завантаження вимкнено"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Невідома редакція %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "Порожній репозиторій"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "Невідомий тип архіву"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Набори змін"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "Гілки"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "Теги"
 
@@ -255,11 +255,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "Під час forking репозиторію %s сталася помилка"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "Групи"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -271,168 +271,168 @@
 msgid "Repositories"
 msgstr "Репозиторії"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Гілка"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "Закриті Гілки"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "Тег"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "Закладка"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Публічний журнал"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Журнал"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Погана капча"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "Ви успішно зареєстровані з  %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Надісланий код підтвердження скидання пароля"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "Недійсний маркер скидання пароля"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "Пароль успішно оновлений"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Вказаний недійсний рецензент \"%s\""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (закрито)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "Набір змін"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "Спеціальний"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "Закладки"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Помилка створення pull request: %s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "Сталася помилка при створенні pull request"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "Новий pull request успішно відкритий"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr "Створено нову ітерацію запиту на pull request"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr "Тим часом додано наступних рецензентів: %s"
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr "Тим часом було видалено наступних рецензентів: %s"
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "Без опису"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "Pull request оновлено"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "Успішно вилучено pull request"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr "Ревізія %s не знайдена в %s"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 "Помилка: changesets не знайдено під час відображення pull request з %s."
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Цей pull request уже об'єднано з  %s."
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr "Цей pull request закрито, його не можна оновити."
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr "Наступні додаткові зміни доступні на %s:"
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "Немає додаткових змін для ітератування на  pull request."
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Примітка: гілка %s має іншу голову: %s."
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 msgid "Git pull requests don't support iterating yet."
 msgstr "Git pull requests не підтримують ітерацію."
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
@@ -440,28 +440,28 @@
 "Помилка: деякі changesets  не знайдені під час відображення pull request "
 "з %s."
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr "Різниця не може бути показана - версії PR не вдалося знайти."
 
+#: kallithea/controllers/search.py:132
+msgid "Invalid search query. Try quoting it."
+msgstr "Неприпустимий пошуковий запит. Спробуйте цитувати його."
+
 #: kallithea/controllers/search.py:136
-msgid "Invalid search query. Try quoting it."
-msgstr "Неприпустимий пошуковий запит. Спробуйте цитувати його."
-
-#: kallithea/controllers/search.py:140
 msgid "The server has no search index."
 msgstr "Сервер не має індексу пошуку."
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "Сталася помилка під час операції пошуку."
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "Дані ще не готові"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "Статистичні дані для цього репозиторію вимкнено"
@@ -474,80 +474,80 @@
 msgid "error occurred during update of auth settings"
 msgstr "під час оновлення параметрів автентифікації сталася помилка"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "Параметри за промовчанням оновлено успішно"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "Сталася помилка під час оновлення за промовчанням"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr "Назавжди"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 хвилин"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 година"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 день"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 місяць"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Постійно"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "Сталася помилка під час створення GIST"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Видалено gist %s"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "Незмінений"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "Зміст gist успішно оновлено"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "Дані gist успішно оновлені"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Сталася помилка під час оновлення gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Ви не можете редагувати цього користувача, оскільки це важливо для всієї "
@@ -558,7 +558,7 @@
 msgstr "Ваш обліковий запис успішно оновлено"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Сталася помилка під час оновлення користувача %s"
@@ -568,44 +568,44 @@
 msgstr "Сталася помилка під час оновлення пароля користувача"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "Додано email %s користувачу"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "Сталася помилка під час збереження електронної пошти"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "Видалено email користувача"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API ключ успішно створений"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "Ключ API успішно скинуто"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API ключ успішно видалений"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 msgid "SSH key successfully deleted"
 msgstr ""
 
@@ -681,11 +681,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "Дозволено з автоматичною активацію облікового запису"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "Ручна Активація зовнішнього акаунту"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "Автоматична Активація зовнішнього акаунту"
 
@@ -707,59 +707,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "Сталася помилка під час оновлення прав"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Сталася помилка при створенні repository group %s"
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr "Створена група репозиторіїв %s"
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Оновлено групу репозиторіїв %s"
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Сталася помилка при оновленні repository group %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Ця група містить %s репозиторії, і їх неможливо видалити"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Ця група містить %s підгрупи і не може бути видалена"
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Видалена група репозиторіїв %s"
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Сталася помилка під час видалення групи репохиторіїв %s"
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Неможливо відкликати дозвіл для себе як адміністратора"
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr "Оновлено дозволи групи репозиторіїв"
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr "Сталася помилка під час відкликання прав"
 
@@ -886,7 +886,7 @@
 msgid "Updated VCS settings"
 msgstr "Оновлені налаштування VCS"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -955,96 +955,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Завдання реіндекса Whoosh заплановано"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr "Створена Група користувачів %s"
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Під час створення групи користувачів  %s сталася помилка"
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr "Оновлена група користувачів %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Сталася помилка під час оновлення групи користувачів %s"
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr "Група користувачів успішно видалена"
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr "Під час видалення групи користувачів сталася помилка"
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr "Цільова група не може бути однаковою"
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr "Права на групи користувачів оновлені"
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr "Оновлені дозволи"
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "Сталася помилка під час збереження дозволів"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr "Створено користувача %s"
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Під час створення користувача %s сталася помилка"
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "Користувач успішно оновлений"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr "Користувач успішно видалений"
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "Сталася помилка під час видалення користувача"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr "Користувача за промовчанням не можна редагувати"
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Додана IP-адреса %s в білий список користувача"
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr "Сталася помилка під час додавання IP-адреси"
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr "Вилучено IP-адресу з білого списку користувачів"
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr "Для виконання цієї дії потрібно бути зареєстрованим користувачем"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "Ви повинні бути зареєстровані для перегляду цієї сторінки"
 
@@ -1077,170 +1077,170 @@
 "Набір змін був занадто великий і було відрізано, використовуйте меню діфф "
 "для показу цього порівняння"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Не виявлено змін"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Видалено гілку: %s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "Створено тег: %s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Набір змін %s не знайдено"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Показати всі комбіновані набори змін %s- >%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr "Порівняйте вигляд"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "і"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s більше"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "редакції"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "Ім'я розгалуження %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull request %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[видалений] репозиторій"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[створено] репозиторій"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[створено] репозиторій як fork"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[forked] репозиторій"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[оновлено] репозиторій"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr "[завантажити] архів з репозиторію"
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[видалити] репозиторій"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[створено] користувач"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[оновлений] користувач"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr "[створено] групу користувачів"
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr "[оновлено] група користувачів"
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Файлів немає"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr "новий файл"
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr "перейменувати"
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1248,34 +1248,36 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1283,7 +1285,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1291,7 +1293,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1299,7 +1301,7 @@
 msgstr[1] "%d днів"
 msgstr[2] "%d дня"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1307,7 +1309,7 @@
 msgstr[1] "%d годин"
 msgstr[2] "%d години"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1315,7 +1317,7 @@
 msgstr[1] "%d хвилин"
 msgstr[2] "%d хвилини"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1323,27 +1325,27 @@
 msgstr[1] "%d секунд"
 msgstr[2] "%d секунди"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "в %s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s тому"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "у %s і %s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s і %s тому"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "прямо зараз"
 
@@ -1352,137 +1354,137 @@
 msgid "on line %s"
 msgstr "в рядку %s"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[Згадування]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr "верхній рівень"
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr "Kallithea Адміністратор"
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr "Користувач за промовчанням не має доступу до нових репозиторіїв"
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
-msgstr "Kallithea Адміністратор"
-
-#: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr "Користувач за промовчанням не має доступу до нових репозиторіїв"
-
-#: kallithea/model/db.py:1640
 msgid "Default user has read access to new repositories"
 msgstr ""
 "Користувач за замовчанням має доступ на перегляд  нових репозиторіїв"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 msgid "Default user has write access to new repositories"
 msgstr ""
 "Користувач за замовчуванням має доступ до запису до нових репозиторіїв"
 
-#: kallithea/model/db.py:1642
+#: kallithea/model/db.py:1639
 msgid "Default user has admin access to new repositories"
 msgstr ""
 "Користувач за промовчанням має доступ адміністратора до нових репозиторіїв"
 
-#: kallithea/model/db.py:1644
+#: kallithea/model/db.py:1641
 msgid "Default user has no access to new repository groups"
 msgstr ""
 "Користувач за замовчуванням не має доступу до нових груп репозиторіїв"
 
-#: kallithea/model/db.py:1645
+#: kallithea/model/db.py:1642
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1644
+msgid "Default user has admin access to new repository groups"
+msgstr ""
+
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
+msgid "Only admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
+msgid "Non-admins can fork repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
+msgid "Registration disabled"
 msgstr ""
 
 #: kallithea/model/db.py:1667
-msgid "Non-admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1669
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/model/db.py:1670
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
-msgid "Not reviewed"
-msgstr ""
-
-#: kallithea/model/db.py:2207
-msgid "Under review"
-msgstr ""
-
 #: kallithea/model/db.py:2208
-msgid "Not approved"
+msgid "Not reviewed"
 msgstr ""
 
 #: kallithea/model/db.py:2209
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2210
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1508,145 +1510,145 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
-#, python-format
-msgid "SSH key %r not found"
-msgstr ""
-
-#: kallithea/model/user.py:186
+#: kallithea/model/ssh_key.py:88
+#, python-format
+msgid "SSH key with fingerprint %r found"
+msgstr ""
+
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2336,7 +2338,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3402,7 +3404,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 msgid "Save Settings"
 msgstr "Зберегти налаштування"
 
@@ -3660,11 +3662,11 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 msgid "Repository page size"
 msgstr "Розмір сторінки репозиторію"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
@@ -3672,11 +3674,11 @@
 "Кількість елементів, що відображаються на сторінках сховища перед "
 "показаним нумерацією."
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr "Розмір адмін сторінки"
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
@@ -3684,33 +3686,33 @@
 "Кількість елементів, що відображаються в сітках адміністратора сторінки "
 "до відображення нумерації."
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "Іконки"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "Показати піктограму загальнодоступного сховища на сховищах"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "Показати значок приватної репозиторію на репозиторіїв"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr "Показати публічні/приватні значки поруч із назвами сховищ."
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 msgid "Meta Tagging"
 msgstr "Мета-теги"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4339,23 +4341,23 @@
 msgid "Merge"
 msgstr "Злити"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr "Замінено на:"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4365,7 +4367,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4375,8 +4377,8 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5342,45 +5344,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "файли"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr "Показати більше"
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr ""
 
+#: kallithea/templates/summary/statistics.html:404
+msgid "files added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:405
+msgid "files changed"
+msgstr "файли змінено"
+
 #: kallithea/templates/summary/statistics.html:406
-msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:407
-msgid "files changed"
-msgstr "файли змінено"
+msgid "files removed"
+msgstr "вилучені файли"
 
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "вилучені файли"
+msgid "commit"
+msgstr "Фіксація"
+
+#: kallithea/templates/summary/statistics.html:409
+msgid "file added"
+msgstr "файл додано"
 
 #: kallithea/templates/summary/statistics.html:410
-msgid "commit"
-msgstr "Фіксація"
+msgid "file changed"
+msgstr "файл змінено"
 
 #: kallithea/templates/summary/statistics.html:411
-msgid "file added"
-msgstr "файл додано"
-
-#: kallithea/templates/summary/statistics.html:412
-msgid "file changed"
-msgstr "файл змінено"
-
-#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "файл видалено"
 
--- a/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2019-08-14 19:00+0000\n"
 "Last-Translator: Elizabeth Sherrock <lizzyd710@gmail.com>\n"
 "Language-Team: Chinese (Simplified) <https://hosted.weblate.org/projects/"
@@ -18,14 +18,14 @@
 "Generated-By: Babel 1.3\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr "还没有修订集"
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,38 +34,38 @@
 msgid "None"
 msgstr "无"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(已关闭)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "显示空白"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "忽略空白"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "增加差异上下文到 %(num)s 行"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 #| msgid "Set changeset status"
 msgid "No permission to change status"
 msgstr "设置修订集状态"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, fuzzy, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "成功删除拉取请求"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr "在此代码库内,此修改并不存在"
 
@@ -78,61 +78,61 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "无响应"
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr "无响应"
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr "未知错误"
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr "由于错误的语法,服务器无法对请求进行响应。"
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "未授权的资源访问"
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "无权访问该页面"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "资源未找到"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr "服务进入非预期的混乱状态,这会阻止它对请求进行响应。"
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr ""
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -140,12 +140,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr "修订集太大并已被截断..."
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s订阅"
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "%s库的修改"
@@ -165,90 +165,90 @@
 msgid "%s at %s"
 msgstr "%s 在 %s"
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr "您只能删除有效分支的修订中的文件"
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "删除文件 %s 通过 Kallithea"
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "成功删除文件 %s"
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr "提交时发生错误"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr "您只能编辑有效分支的修订中的文件"
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "已编辑文件 %s 通过 Kallithea"
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "无变更"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "成功提交到%s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr "已添加文件通过 Kallithea"
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr "无内容"
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr "无文件名"
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr "下载已禁用"
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "未知版本%s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "空版本库"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "未知包类型"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "修订集"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "分支"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "标签"
 
@@ -257,11 +257,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr "在复刻版本库%s的时候发生错误"
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr "组"
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -273,201 +273,201 @@
 msgid "Repositories"
 msgstr "版本库"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "分支"
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr "已关闭分支"
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr "标签"
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr "书签"
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "公共日志"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "日志"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "验证码错误"
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr "您已成功注册 %s"
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "密码重置确认码已经发送"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "无效的密码重置令牌"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr "成功更新密码"
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "指定的审核者 \"%s\" 无效"
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (已关闭)"
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr "修订集"
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr "特殊"
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr "同等分支"
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr "书签"
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "创建拉取请求出错:%s"
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr "创建拉取请求时发生错误"
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr "成功提交拉取请求"
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 #, fuzzy
 #| msgid "Pull request update created"
 msgid "New pull request iteration created"
 msgstr "拉取请求更新已创建"
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "无描述"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr "拉取请求已更新"
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr "成功删除拉取请求"
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, fuzzy, python-format
 #| msgid "Changeset for %s %s not found in %s"
 msgid "Revision %s not found in %s"
 msgstr "未找到修订集"
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr "没有找到更新此拉取请求的修订集。"
 
-#: kallithea/controllers/pullrequests.py:520
+#: kallithea/controllers/pullrequests.py:518
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:522
+#: kallithea/controllers/pullrequests.py:520
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 #, fuzzy
 #| msgid "No changesets found for updating this pull request."
 msgid "No additional changesets found for iterating on this pull request."
 msgstr "没有找到更新此拉取请求的修订集。"
 
-#: kallithea/controllers/pullrequests.py:560
+#: kallithea/controllers/pullrequests.py:553
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:567
+#: kallithea/controllers/pullrequests.py:560
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, fuzzy, python-format
 #| msgid "No changesets found for updating this pull request."
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr "没有找到更新此拉取请求的修订集。"
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "错误的搜索。请尝试用引号包含它。"
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr "搜索操作期间发生错误。"
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr "数据尚未就绪"
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "该版本库统计功能已经禁用"
@@ -480,81 +480,81 @@
 msgid "error occurred during update of auth settings"
 msgstr "验证设置更新时发生错误"
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr "默认设置已经成功更新"
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr "默认值更新时发生错误"
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 #, fuzzy
 msgid "Forever"
 msgstr "检视者"
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr "5 分钟"
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr "1 小时"
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr "1 天"
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr "1 个月"
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "终身"
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr "gist 创建时发生错误"
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr "已删除 gist %s"
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "未修改"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr "成功更新 gist 内容"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr "成功更新 gist 数据"
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "gist %s 更新时发生错误"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr "由于是系统帐号,无法编辑该用户"
 
@@ -563,7 +563,7 @@
 msgstr "你的帐号已经更新完成"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "用户 %s 更新时发生错误"
@@ -573,45 +573,45 @@
 msgstr "用户密码更新时发生错误"
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr "已为用户添加电子邮件 %s"
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr "保存电子邮件时发生错误"
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr "成功删除用户电子邮件"
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr "API 密钥创建成功"
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr "API 密钥重置成功"
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr "API 密钥删除成功"
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, fuzzy, python-format
 #| msgid "API key successfully created"
 msgid "SSH key %s successfully added"
 msgstr "API 密钥创建成功"
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 #| msgid "API key successfully deleted"
 msgid "SSH key successfully deleted"
@@ -689,11 +689,11 @@
 msgid "Allowed with automatic account activation"
 msgstr "已允许自动激活账号"
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr "外部账号手动激活"
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr "外部账号自动激活"
 
@@ -715,59 +715,59 @@
 msgid "Error occurred during update of permissions"
 msgstr "权限更新时发生错误"
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "这个组内有%s个版本库因而无法删除"
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -894,7 +894,7 @@
 msgid "Updated VCS settings"
 msgstr "成功更新版本控制系统设置"
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -961,96 +961,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh重新索引任务调度"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr "保存权限时发生错误"
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "用户更新成功"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr "删除用户时发生错误"
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr "必须是注册用户才能进行此操作"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "必须登录才能访问该页面"
 
@@ -1083,171 +1083,171 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr "修订集过大并已被截断,使用差异菜单查看此差异"
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "未发现差异"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "已经删除分支%s"
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr "创建标签%s"
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 msgid "Changeset %s not found"
 msgstr "未找到修订集"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "显示所有合并的修订集 %s->%s"
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 #, fuzzy
 msgid "Compare view"
 msgstr "比较显示"
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "还有"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr "%s个"
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "修订"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, fuzzy, python-format
 msgid "Fork name %s"
 msgstr "复刻名称%s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, fuzzy, python-format
 msgid "Pull request %s"
 msgstr "拉取请求#%s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr "[删除]版本库"
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr "[创建]版本库"
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr "[创建]复刻版本库"
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr "[复刻]版本库"
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr "[更新]版本库"
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr "[删除]版本库"
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr "[创建]用户"
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr "[更新]用户"
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr "[评论]了版本库中的修订"
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr "[评论]拉取请求"
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr "[关闭] 拉取请求"
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr "[推送]到"
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr "[通过Kallithea提交]到版本库"
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr "[远程拉取]到版本库"
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr "[拉取]自"
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr "[开始关注]版本库"
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr "[停止关注]版本库"
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr " 还有%s个"
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "无文件"
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1257,90 +1257,92 @@
 "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启Kallithea"
 "以重新扫描版本库"
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d年"
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d月"
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d天"
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d时"
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d分"
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d秒"
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr "%s"
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr "%s前"
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr "%s零%s"
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s零%s前"
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "刚才"
 
@@ -1349,143 +1351,143 @@
 msgid "on line %s"
 msgstr "在%s行"
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr "[提及]"
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr "Kallithea 管理员"
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
-msgstr "Kallithea 管理员"
-
-#: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "未授权的资源访问"
 
-#: kallithea/model/db.py:1641
+#: kallithea/model/db.py:1638
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "未授权的资源访问"
 
+#: kallithea/model/db.py:1639
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1641
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1654
 #, fuzzy
 msgid "Only admins can create repository groups"
 msgstr "没有在该版本库组中创建版本库的权限"
 
-#: kallithea/model/db.py:1655
+#: kallithea/model/db.py:1652
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr "没有在该版本库组中创建版本库的权限"
 
+#: kallithea/model/db.py:1654
+msgid "Only admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1655
+msgid "Non-admins can create user groups"
+msgstr ""
+
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1666
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "创建版本库"
 
-#: kallithea/model/db.py:1667
+#: kallithea/model/db.py:1664
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "创建版本库"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 #, fuzzy
 msgid "Not reviewed"
 msgstr "未检视"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 #, fuzzy
 msgid "Under review"
 msgstr "检视中"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 #, fuzzy
 #| msgid "Approved"
 msgid "Not approved"
 msgstr "已批准"
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr "已批准"
 
@@ -1511,7 +1513,7 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, fuzzy, python-format
 #| msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
 msgid ""
@@ -1519,119 +1521,119 @@
 "%(branch)s"
 msgstr "[评论] %(repo_name)s 修订集 %(short_id)s 在 %(branch)s"
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, fuzzy, python-format
 msgid "New user %(new_username)s registered"
 msgstr "用户名称 %(new_username)s 无效"
 
-#: kallithea/model/notification.py:169
+#: kallithea/model/notification.py:168
 #, python-format
 msgid ""
 "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:170
+#: kallithea/model/notification.py:169
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 #, fuzzy
 msgid "Closing"
 msgstr "使用中"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 #, fuzzy
 #| msgid "Error creating pull request: %s"
 msgid "Cannot create empty pull request"
 msgstr "创建拉取请求出错:%s"
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 #, fuzzy
 #| msgid "Confirm to delete this pull request"
 msgid "You are not authorized to create the pull request"
 msgstr "确认删除拉取请求"
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 #, fuzzy
 #| msgid "Missing changesets since the previous pull request:"
 msgid "Missing changesets since the previous iteration:"
 msgstr "缺少上次拉取请求之后的修订集:"
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, fuzzy, python-format
 #| msgid "New changesets on %s %s since the previous pull request:"
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr "在上次拉取请求之后,在 %s %s 上的新修订集:"
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, fuzzy, python-format
 #| msgid "New changesets on %s %s since the previous pull request:"
 msgid "No changes found on %s %s since previous iteration."
 msgstr "在上次拉取请求之后,在 %s %s 上的新修订集:"
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr "最新tip版本"
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "未找到修订集"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 #, fuzzy
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr "由于是系统帐号,无法删除该用户"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -1640,7 +1642,7 @@
 "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本"
 "库。%s"
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
@@ -1649,7 +1651,7 @@
 "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本"
 "库。%s"
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -1658,16 +1660,16 @@
 "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本"
 "库。%s"
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 #, fuzzy
 msgid "Password reset notification"
 msgstr "确认密码"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2367,7 +2369,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3462,7 +3464,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 #, fuzzy
 msgid "Save Settings"
 msgstr "保存设置"
@@ -3711,57 +3713,57 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "repositories"
 msgid "Repository page size"
 msgstr "版本库"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr "图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr "显示公共版本库图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr "显示私有版本库图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 #, fuzzy
 msgid "Show public/private icons next to repository names."
 msgstr "显示公共版本库图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "元标记"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4415,26 +4417,26 @@
 msgid "Merge"
 msgstr "合并"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 #, fuzzy
 msgid "Grafted from:"
 msgstr "创建于"
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 #, fuzzy
 msgid "Replaced by:"
 msgstr "创建于"
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 #, fuzzy
 msgid "Preceded by:"
 msgstr "创建于"
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4442,7 +4444,7 @@
 msgid_plural "%s files changed"
 msgstr[0] "修改%s个文件"
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4450,8 +4452,8 @@
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "修改%s个文件包括%s行插入和%s行删除"
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5474,45 +5476,45 @@
 msgid "Stats gathered: "
 msgstr "已收集的统计: "
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "文件"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "提交"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "文件已添加"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "文件已更改"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "文件已删除"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "文件已删除"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "提交"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "文件已添加"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "文件已更改"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "文件已删除"
 
--- a/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po	Thu Feb 06 01:19:23 2020 +0100
@@ -4,7 +4,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2019-11-14 23:33+0100\n"
+"POT-Creation-Date: 2020-02-06 01:19+0100\n"
 "PO-Revision-Date: 2017-03-10 18:26+0000\n"
 "Last-Translator: mao <mao@lins.fju.edu.tw>\n"
 "Language-Team: Chinese (Traditional) <https://hosted.weblate.org/projects/"
@@ -18,14 +18,14 @@
 "Generated-By: Babel 1.3\n"
 
 #: kallithea/controllers/changelog.py:67
-#: kallithea/controllers/pullrequests.py:250 kallithea/lib/base.py:602
+#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:602
 msgid "There are no changesets yet"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
-#: kallithea/controllers/changelog.py:137
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:7
@@ -34,37 +34,37 @@
 msgid "None"
 msgstr "無"
 
-#: kallithea/controllers/changelog.py:140 kallithea/controllers/files.py:189
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:189
 msgid "(closed)"
 msgstr "(已關閉)"
 
-#: kallithea/controllers/changeset.py:81
+#: kallithea/controllers/changeset.py:82
 msgid "Show whitespace"
 msgstr "顯示空格"
 
-#: kallithea/controllers/changeset.py:88
-#: kallithea/controllers/changeset.py:95
+#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:96
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "忽略空格"
 
-#: kallithea/controllers/changeset.py:161
+#: kallithea/controllers/changeset.py:162
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "增加 diff 上下文至 %(num)s 行"
 
-#: kallithea/controllers/changeset.py:201
+#: kallithea/controllers/changeset.py:202
 #, fuzzy
 msgid "No permission to change status"
 msgstr "尚未有任何變更"
 
-#: kallithea/controllers/changeset.py:212
+#: kallithea/controllers/changeset.py:213
 #, fuzzy, python-format
 msgid "Successfully deleted pull request %s"
 msgstr "成功遞交至 %s"
 
-#: kallithea/controllers/changeset.py:319 kallithea/controllers/files.py:89
-#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:700
+#: kallithea/controllers/changeset.py:320 kallithea/controllers/files.py:89
+#: kallithea/controllers/files.py:109 kallithea/controllers/files.py:697
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
@@ -78,61 +78,61 @@
 msgid "Cannot compare repositories of different types"
 msgstr ""
 
-#: kallithea/controllers/compare.py:246
+#: kallithea/controllers/compare.py:247
 msgid "Cannot show empty diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:248
+#: kallithea/controllers/compare.py:249
 msgid "No ancestor found for merge diff"
 msgstr ""
 
-#: kallithea/controllers/compare.py:252
+#: kallithea/controllers/compare.py:253
 msgid "Multiple merge ancestors found for merge compare"
 msgstr ""
 
-#: kallithea/controllers/compare.py:268
+#: kallithea/controllers/compare.py:269
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "未回應"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:85
+#: kallithea/controllers/error.py:84
 msgid ""
 "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:88
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:90
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "您沒有權限瀏覽這個頁面"
 
-#: kallithea/controllers/error.py:92
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "找不到這個資源"
 
-#: kallithea/controllers/error.py:94
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:63
+#: kallithea/controllers/feed.py:59
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s 評論於 %s"
 
-#: kallithea/controllers/feed.py:88
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/controllers/feed.py:84
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/compare/compare_diff.html:95
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
@@ -140,12 +140,12 @@
 msgid "Changeset was too big and was cut off..."
 msgstr ""
 
-#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:140
+#: kallithea/controllers/feed.py:107
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:142
+#: kallithea/controllers/feed.py:109
 #, python-format
 msgid "Changes on %s repository"
 msgstr "修改於版本庫 %s"
@@ -165,90 +165,90 @@
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:296
+#: kallithea/controllers/files.py:295
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:307
+#: kallithea/controllers/files.py:306
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:331
+#: kallithea/controllers/files.py:330
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:335 kallithea/controllers/files.py:394
-#: kallithea/controllers/files.py:469
+#: kallithea/controllers/files.py:334 kallithea/controllers/files.py:392
+#: kallithea/controllers/files.py:467
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:349
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:364
+#: kallithea/controllers/files.py:363
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:380
+#: kallithea/controllers/files.py:378
 msgid "No changes"
 msgstr "沒有修改"
 
-#: kallithea/controllers/files.py:390 kallithea/controllers/files.py:458
+#: kallithea/controllers/files.py:388 kallithea/controllers/files.py:456
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "成功遞交至 %s"
 
-#: kallithea/controllers/files.py:409
+#: kallithea/controllers/files.py:407
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:430
+#: kallithea/controllers/files.py:428
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:434
+#: kallithea/controllers/files.py:432
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:461
+#: kallithea/controllers/files.py:459
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:491
 msgid "Downloads disabled"
 msgstr ""
 
-#: kallithea/controllers/files.py:504
+#: kallithea/controllers/files.py:502
 #, python-format
 msgid "Unknown revision %s"
 msgstr "未知修訂 %s"
 
-#: kallithea/controllers/files.py:506
+#: kallithea/controllers/files.py:504
 msgid "Empty repository"
 msgstr "空的版本庫"
 
-#: kallithea/controllers/files.py:508
+#: kallithea/controllers/files.py:506
 msgid "Unknown archive type"
 msgstr "未知的存檔類型"
 
-#: kallithea/controllers/files.py:729
+#: kallithea/controllers/files.py:726
 #: kallithea/templates/changeset/changeset_range.html:9
 #: kallithea/templates/email_templates/pull_request.html:64
 #: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "變更"
 
-#: kallithea/controllers/files.py:730
-#: kallithea/controllers/pullrequests.py:182 kallithea/model/scm.py:676
+#: kallithea/controllers/files.py:727
+#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:663
 msgid "Branches"
 msgstr "分支"
 
-#: kallithea/controllers/files.py:731
-#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:687
+#: kallithea/controllers/files.py:728
+#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:674
 msgid "Tags"
 msgstr "標籤"
 
@@ -257,11 +257,11 @@
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:79
+#: kallithea/controllers/home.py:77
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
+#: kallithea/controllers/home.py:87
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
@@ -273,194 +273,194 @@
 msgid "Repositories"
 msgstr "版本庫"
 
-#: kallithea/controllers/home.py:122
+#: kallithea/controllers/home.py:119
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:128
+#: kallithea/controllers/home.py:125
 msgid "Closed Branches"
 msgstr ""
 
-#: kallithea/controllers/home.py:134
+#: kallithea/controllers/home.py:131
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:140
+#: kallithea/controllers/home.py:137
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:112 kallithea/controllers/journal.py:154
+#: kallithea/controllers/journal.py:146 kallithea/controllers/journal.py:157
 #: kallithea/templates/journal/public_journal.html:4
 #: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "開放日誌"
 
-#: kallithea/controllers/journal.py:116 kallithea/controllers/journal.py:158
+#: kallithea/controllers/journal.py:150 kallithea/controllers/journal.py:161
 #: kallithea/templates/base/base.html:290
 #: kallithea/templates/journal/journal.html:5
 #: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "日誌"
 
-#: kallithea/controllers/login.py:140 kallithea/controllers/login.py:185
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:146
+#: kallithea/controllers/login.py:145
 #, python-format
 msgid "You have successfully registered with %s"
 msgstr ""
 
-#: kallithea/controllers/login.py:190
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "密碼重設的確認碼已寄出"
 
-#: kallithea/controllers/login.py:239
+#: kallithea/controllers/login.py:236
 msgid "Invalid password reset token"
 msgstr "無效的密碼重設確認碼"
 
 #: kallithea/controllers/admin/my_account.py:157
-#: kallithea/controllers/login.py:244
+#: kallithea/controllers/login.py:241
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:68
+#: kallithea/controllers/pullrequests.py:67
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:131
+#: kallithea/controllers/pullrequests.py:123
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:158
+#: kallithea/controllers/pullrequests.py:150
 #: kallithea/templates/changeset/changeset.html:12
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:179
+#: kallithea/controllers/pullrequests.py:171
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:180
+#: kallithea/controllers/pullrequests.py:172
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:682
+#: kallithea/controllers/pullrequests.py:173 kallithea/model/scm.py:669
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:318
+#: kallithea/controllers/pullrequests.py:315
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:345
-#: kallithea/controllers/pullrequests.py:368
+#: kallithea/controllers/pullrequests.py:342
+#: kallithea/controllers/pullrequests.py:365
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:350
+#: kallithea/controllers/pullrequests.py:347
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:373
+#: kallithea/controllers/pullrequests.py:370
 msgid "New pull request iteration created"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
+#: kallithea/controllers/pullrequests.py:398
 #, python-format
 msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:405
+#: kallithea/controllers/pullrequests.py:402
 #, python-format
 msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:421
-#: kallithea/model/pull_request.py:232
+#: kallithea/controllers/pullrequests.py:418
+#: kallithea/model/pull_request.py:230
 msgid "No description"
 msgstr "無描述"
 
-#: kallithea/controllers/pullrequests.py:430
+#: kallithea/controllers/pullrequests.py:427
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:443
+#: kallithea/controllers/pullrequests.py:440
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:479
+#: kallithea/controllers/pullrequests.py:476
 #, python-format
 msgid "Revision %s not found in %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:506
+#: kallithea/controllers/pullrequests.py:504
 #, python-format
 msgid "Error: changesets not found when displaying pull request from %s."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:518
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:520
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:522
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:546
+#: kallithea/controllers/pullrequests.py:539
 #, python-format
 msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:548
-#: kallithea/controllers/pullrequests.py:552
+#: kallithea/controllers/pullrequests.py:541
+#: kallithea/controllers/pullrequests.py:545
 msgid "No additional changesets found for iterating on this pull request."
 msgstr ""
 
+#: kallithea/controllers/pullrequests.py:553
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
 #: kallithea/controllers/pullrequests.py:560
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:567
 msgid "Git pull requests don't support iterating yet."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:569
+#: kallithea/controllers/pullrequests.py:562
 #, python-format
 msgid ""
 "Error: some changesets not found when displaying pull request from %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:593
+#: kallithea/controllers/pullrequests.py:586
 msgid "The diff can't be shown - the PR revisions could not be found."
 msgstr ""
 
-#: kallithea/controllers/search.py:136
+#: kallithea/controllers/search.py:132
 msgid "Invalid search query. Try quoting it."
 msgstr "無效的查詢。請使用跳脫字元。"
 
-#: kallithea/controllers/search.py:140
+#: kallithea/controllers/search.py:136
 msgid "The server has no search index."
 msgstr ""
 
-#: kallithea/controllers/search.py:143
+#: kallithea/controllers/search.py:139
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:168
-#: kallithea/templates/summary/summary.html:412
+#: kallithea/controllers/summary.py:169
+#: kallithea/templates/summary/summary.html:410
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:171
+#: kallithea/controllers/summary.py:172
 #: kallithea/templates/summary/summary.html:97
 msgid "Statistics are disabled for this repository"
 msgstr "這個版本庫的統計功能已停用"
@@ -473,80 +473,80 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:75
+#: kallithea/controllers/admin/defaults.py:74
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:90
+#: kallithea/controllers/admin/defaults.py:89
 msgid "Error occurred during update of defaults"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:59
 #: kallithea/controllers/admin/my_account.py:232
-#: kallithea/controllers/admin/users.py:248
+#: kallithea/controllers/admin/users.py:246
 msgid "Forever"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
 #: kallithea/controllers/admin/my_account.py:233
-#: kallithea/controllers/admin/users.py:249
+#: kallithea/controllers/admin/users.py:247
 msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
 #: kallithea/controllers/admin/my_account.py:234
-#: kallithea/controllers/admin/users.py:250
+#: kallithea/controllers/admin/users.py:248
 msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
 #: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:251
+#: kallithea/controllers/admin/users.py:249
 msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:63
 #: kallithea/controllers/admin/my_account.py:236
-#: kallithea/controllers/admin/users.py:252
+#: kallithea/controllers/admin/users.py:250
 msgid "1 month"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:67
 #: kallithea/controllers/admin/my_account.py:238
-#: kallithea/controllers/admin/users.py:254
+#: kallithea/controllers/admin/users.py:252
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:65
 #: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:142
+#: kallithea/controllers/admin/gists.py:148
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:158
+#: kallithea/controllers/admin/gists.py:164
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:198
+#: kallithea/controllers/admin/gists.py:207
 msgid "Unmodified"
 msgstr "未修改"
 
-#: kallithea/controllers/admin/gists.py:228
+#: kallithea/controllers/admin/gists.py:237
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:242
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:236
+#: kallithea/controllers/admin/gists.py:245
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:211
-#: kallithea/model/user.py:232
+#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:209
+#: kallithea/model/user.py:230
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
@@ -555,7 +555,7 @@
 msgstr "您的帳號已更新完成"
 
 #: kallithea/controllers/admin/my_account.py:134
-#: kallithea/controllers/admin/users.py:181
+#: kallithea/controllers/admin/users.py:179
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
@@ -565,44 +565,44 @@
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:209
-#: kallithea/controllers/admin/users.py:367
+#: kallithea/controllers/admin/users.py:365
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:215
-#: kallithea/controllers/admin/users.py:373
+#: kallithea/controllers/admin/users.py:371
 msgid "An error occurred during email saving"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:224
-#: kallithea/controllers/admin/users.py:383
+#: kallithea/controllers/admin/users.py:381
 msgid "Removed email from user"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:248
-#: kallithea/controllers/admin/users.py:271
+#: kallithea/controllers/admin/users.py:269
 msgid "API key successfully created"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:257
-#: kallithea/controllers/admin/users.py:281
+#: kallithea/controllers/admin/users.py:279
 msgid "API key successfully reset"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:261
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/users.py:283
 msgid "API key successfully deleted"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:281
-#: kallithea/controllers/admin/users.py:456
+#: kallithea/controllers/admin/users.py:454
 #, python-format
 msgid "SSH key %s successfully added"
 msgstr ""
 
 #: kallithea/controllers/admin/my_account.py:293
-#: kallithea/controllers/admin/users.py:470
+#: kallithea/controllers/admin/users.py:468
 #, fuzzy
 msgid "SSH key successfully deleted"
 msgstr "成功遞交至 %s"
@@ -679,11 +679,11 @@
 msgid "Allowed with automatic account activation"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1673
+#: kallithea/controllers/admin/permissions.py:85 kallithea/model/db.py:1670
 msgid "Manual activation of external account"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1674
+#: kallithea/controllers/admin/permissions.py:86 kallithea/model/db.py:1671
 msgid "Automatic activation of external account"
 msgstr ""
 
@@ -705,59 +705,59 @@
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:172
+#: kallithea/controllers/admin/repo_groups.py:167
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:177
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:224
+#: kallithea/controllers/admin/repo_groups.py:221
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:240
+#: kallithea/controllers/admin/repo_groups.py:237
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:247
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:257
+#: kallithea/controllers/admin/repo_groups.py:254
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:263
+#: kallithea/controllers/admin/repo_groups.py:260
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:268
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:352
-#: kallithea/controllers/admin/repo_groups.py:382
-#: kallithea/controllers/admin/user_groups.py:294
+#: kallithea/controllers/admin/repo_groups.py:349
+#: kallithea/controllers/admin/repo_groups.py:379
+#: kallithea/controllers/admin/user_groups.py:292
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:367
+#: kallithea/controllers/admin/repo_groups.py:364
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:399
+#: kallithea/controllers/admin/repo_groups.py:396
 #: kallithea/controllers/admin/repos.py:358
-#: kallithea/controllers/admin/user_groups.py:306
+#: kallithea/controllers/admin/user_groups.py:304
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
@@ -883,7 +883,7 @@
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:237
+#: kallithea/controllers/admin/settings.py:135 kallithea/lib/utils.py:238
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -949,96 +949,96 @@
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh 重新索引工作排程"
 
-#: kallithea/controllers/admin/user_groups.py:138
+#: kallithea/controllers/admin/user_groups.py:136
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:151
+#: kallithea/controllers/admin/user_groups.py:149
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:179
+#: kallithea/controllers/admin/user_groups.py:177
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:199
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:212
+#: kallithea/controllers/admin/user_groups.py:210
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:217
+#: kallithea/controllers/admin/user_groups.py:215
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:273
+#: kallithea/controllers/admin/user_groups.py:271
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:279
+#: kallithea/controllers/admin/user_groups.py:277
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:388
-#: kallithea/controllers/admin/users.py:338
+#: kallithea/controllers/admin/user_groups.py:386
+#: kallithea/controllers/admin/users.py:336
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:392
-#: kallithea/controllers/admin/users.py:342
+#: kallithea/controllers/admin/user_groups.py:390
+#: kallithea/controllers/admin/users.py:340
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:123
+#: kallithea/controllers/admin/users.py:121
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:138
+#: kallithea/controllers/admin/users.py:136
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:162
+#: kallithea/controllers/admin/users.py:160
 msgid "User updated successfully"
 msgstr "使用者更新完成"
 
-#: kallithea/controllers/admin/users.py:190
+#: kallithea/controllers/admin/users.py:188
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:195
+#: kallithea/controllers/admin/users.py:193
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:203
+#: kallithea/controllers/admin/users.py:201
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:409
+#: kallithea/controllers/admin/users.py:407
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/users.py:413
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:427
+#: kallithea/controllers/admin/users.py:425
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:684
+#: kallithea/lib/auth.py:668
 msgid "You need to be a registered user to perform this action"
 msgstr "您必須是註冊使用者才能執行這個動作"
 
-#: kallithea/lib/auth.py:712
+#: kallithea/lib/auth.py:696
 msgid "You need to be signed in to view this page"
 msgstr "您必須登入後才能瀏覽這個頁面"
 
@@ -1071,171 +1071,171 @@
 "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:224
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "尚未有任何變更"
 
-#: kallithea/lib/helpers.py:653
+#: kallithea/lib/helpers.py:646
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:655
+#: kallithea/lib/helpers.py:648
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:666
+#: kallithea/lib/helpers.py:659
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "尚未有任何變更"
 
-#: kallithea/lib/helpers.py:715
+#: kallithea/lib/helpers.py:708
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:721
+#: kallithea/lib/helpers.py:714
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:740
+#: kallithea/lib/helpers.py:733
 msgid "and"
 msgstr "和"
 
-#: kallithea/lib/helpers.py:741
+#: kallithea/lib/helpers.py:734
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:742
+#: kallithea/lib/helpers.py:735
 #: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "修訂"
 
-#: kallithea/lib/helpers.py:766
+#: kallithea/lib/helpers.py:759
 #, python-format
 msgid "Fork name %s"
 msgstr "分支名稱 %s"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:780
 #, python-format
 msgid "Pull request %s"
 msgstr "提取要求 %s"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:790
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:799 kallithea/lib/helpers.py:811
+#: kallithea/lib/helpers.py:792 kallithea/lib/helpers.py:804
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:801
+#: kallithea/lib/helpers.py:794
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:803 kallithea/lib/helpers.py:813
+#: kallithea/lib/helpers.py:796 kallithea/lib/helpers.py:806
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:805 kallithea/lib/helpers.py:815
+#: kallithea/lib/helpers.py:798 kallithea/lib/helpers.py:808
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:807
+#: kallithea/lib/helpers.py:800
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:809
+#: kallithea/lib/helpers.py:802
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:817
+#: kallithea/lib/helpers.py:810
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:819
+#: kallithea/lib/helpers.py:812
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:821
+#: kallithea/lib/helpers.py:814
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:823
+#: kallithea/lib/helpers.py:816
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:825
+#: kallithea/lib/helpers.py:818
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:827
+#: kallithea/lib/helpers.py:820
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:829
+#: kallithea/lib/helpers.py:822
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:831
+#: kallithea/lib/helpers.py:824
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:833
+#: kallithea/lib/helpers.py:826
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:835
+#: kallithea/lib/helpers.py:828
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:837
+#: kallithea/lib/helpers.py:830
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:839
+#: kallithea/lib/helpers.py:832
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:841
+#: kallithea/lib/helpers.py:834
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:961
+#: kallithea/lib/helpers.py:954
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:965
+#: kallithea/lib/helpers.py:958
 #: kallithea/templates/compare/compare_diff.html:69
 #: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:990
+#: kallithea/lib/helpers.py:983
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:993
+#: kallithea/lib/helpers.py:986
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:996
+#: kallithea/lib/helpers.py:989
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:999
+#: kallithea/lib/helpers.py:992
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1004
+#: kallithea/lib/helpers.py:997
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1297
+#: kallithea/lib/helpers.py:1290
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1243,90 +1243,92 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/ssh.py:71
+#: kallithea/lib/ssh.py:75
 msgid "SSH key is missing"
 msgstr ""
 
-#: kallithea/lib/ssh.py:75
-msgid "Incorrect SSH key - it must have both a key type and a base64 part"
-msgstr ""
-
 #: kallithea/lib/ssh.py:79
+msgid ""
+"Incorrect SSH key - it must have both a key type and a base64 part, like "
+"'ssh-rsa ASRNeaZu4FA...xlJp='"
+msgstr ""
+
+#: kallithea/lib/ssh.py:83
 msgid "Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'"
 msgstr ""
 
-#: kallithea/lib/ssh.py:82
+#: kallithea/lib/ssh.py:86
 #, python-format
 msgid "Incorrect SSH key - unexpected characters in base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:87
+#: kallithea/lib/ssh.py:91
 #, python-format
 msgid "Incorrect SSH key - failed to decode base64 part %r"
 msgstr ""
 
-#: kallithea/lib/ssh.py:90
+#: kallithea/lib/ssh.py:94
 #, python-format
 msgid "Incorrect SSH key - base64 part is not %r as claimed but %r"
 msgstr ""
 
-#: kallithea/lib/utils2.py:334
+#: kallithea/lib/utils2.py:242
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:335
+#: kallithea/lib/utils2.py:243
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:336
+#: kallithea/lib/utils2.py:244
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:337
+#: kallithea/lib/utils2.py:245
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:338
+#: kallithea/lib/utils2.py:246
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:339
+#: kallithea/lib/utils2.py:247
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:355
+#: kallithea/lib/utils2.py:263
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:357
+#: kallithea/lib/utils2.py:265
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:359
+#: kallithea/lib/utils2.py:267
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:362
+#: kallithea/lib/utils2.py:270
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:365
+#: kallithea/lib/utils2.py:273
 msgid "just now"
 msgstr "現在"
 
@@ -1335,134 +1337,134 @@
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:114
+#: kallithea/model/comment.py:219 kallithea/model/pull_request.py:112
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1496
+#: kallithea/model/db.py:1493
 msgid "top level"
 msgstr ""
 
+#: kallithea/model/db.py:1634
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1636
+msgid "Default user has no access to new repositories"
+msgstr ""
+
 #: kallithea/model/db.py:1637
-msgid "Kallithea Administrator"
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1638
+msgid "Default user has write access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1639
-msgid "Default user has no access to new repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1640
-msgid "Default user has read access to new repositories"
+msgid "Default user has admin access to new repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1641
-msgid "Default user has write access to new repositories"
+msgid "Default user has no access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1642
-msgid "Default user has admin access to new repositories"
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1643
+msgid "Default user has write access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1644
-msgid "Default user has no access to new repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1645
-msgid "Default user has read access to new repository groups"
+msgid "Default user has admin access to new repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1646
-msgid "Default user has write access to new repository groups"
+msgid "Default user has no access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1647
-msgid "Default user has admin access to new repository groups"
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1648
+msgid "Default user has write access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1649
-msgid "Default user has no access to new user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1650
-msgid "Default user has read access to new user groups"
+msgid "Default user has admin access to new user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1651
-msgid "Default user has write access to new user groups"
+msgid "Only admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1652
-msgid "Default user has admin access to new user groups"
+msgid "Non-admins can create repository groups"
 msgstr ""
 
 #: kallithea/model/db.py:1654
-msgid "Only admins can create repository groups"
+msgid "Only admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1655
-msgid "Non-admins can create repository groups"
+msgid "Non-admins can create user groups"
 msgstr ""
 
 #: kallithea/model/db.py:1657
-msgid "Only admins can create user groups"
+msgid "Only admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1658
-msgid "Non-admins can create user groups"
+msgid "Non-admins can create top level repositories"
 msgstr ""
 
 #: kallithea/model/db.py:1660
-msgid "Only admins can create top level repositories"
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1661
-msgid "Non-admins can create top level repositories"
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 
 #: kallithea/model/db.py:1663
-msgid ""
-"Repository creation enabled with write permission to a repository group"
-msgstr ""
+msgid "Only admins can fork repositories"
+msgstr "祗有管理者才能分歧版本庫"
 
 #: kallithea/model/db.py:1664
-msgid ""
-"Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/db.py:1666
-msgid "Only admins can fork repositories"
-msgstr "祗有管理者才能分歧版本庫"
-
-#: kallithea/model/db.py:1667
 #, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "建立版本庫"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1666
 msgid "Registration disabled"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1667
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1668
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2206
+#: kallithea/model/db.py:2208
 msgid "Not reviewed"
 msgstr "未審核"
 
-#: kallithea/model/db.py:2207
+#: kallithea/model/db.py:2209
 msgid "Under review"
 msgstr "審核中"
 
-#: kallithea/model/db.py:2208
+#: kallithea/model/db.py:2210
 msgid "Not approved"
 msgstr ""
 
-#: kallithea/model/db.py:2209
+#: kallithea/model/db.py:2211
 msgid "Approved"
 msgstr ""
 
@@ -1488,146 +1490,146 @@
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:164
+#: kallithea/model/notification.py:163
 #, python-format
 msgid ""
 "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
 "%(branch)s"
 msgstr ""
 
-#: kallithea/model/notification.py:167
+#: kallithea/model/notification.py:166
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
+#: kallithea/model/notification.py:168
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
 #: kallithea/model/notification.py:169
 #, python-format
 msgid ""
-"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
-"%(pr_source_branch)s by %(pr_owner_username)s"
-msgstr ""
-
-#: kallithea/model/notification.py:170
-#, python-format
-msgid ""
 "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
 "%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
 
-#: kallithea/model/notification.py:183
+#: kallithea/model/notification.py:189
 msgid "Closing"
 msgstr "關閉中"
 
-#: kallithea/model/pull_request.py:73
+#: kallithea/model/pull_request.py:72
 #, python-format
 msgid ""
 "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:209
+#: kallithea/model/pull_request.py:207
 msgid "Cannot create empty pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:217
+#: kallithea/model/pull_request.py:215
 #, python-format
 msgid ""
 "Cannot create pull request - criss cross merge detected, please merge a "
 "later %s revision to %s"
 msgstr ""
 
-#: kallithea/model/pull_request.py:245 kallithea/model/pull_request.py:380
+#: kallithea/model/pull_request.py:243 kallithea/model/pull_request.py:378
 msgid "You are not authorized to create the pull request"
 msgstr ""
 
-#: kallithea/model/pull_request.py:339
+#: kallithea/model/pull_request.py:337
 msgid "Missing changesets since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:346
+#: kallithea/model/pull_request.py:344
 #, python-format
 msgid "New changesets on %s %s since the previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:353
+#: kallithea/model/pull_request.py:351
 msgid "Ancestor didn't change - diff since previous iteration:"
 msgstr ""
 
-#: kallithea/model/pull_request.py:360
+#: kallithea/model/pull_request.py:358
 #, python-format
 msgid ""
 "This iteration is based on another %s revision and there is no simple "
 "diff."
 msgstr ""
 
-#: kallithea/model/pull_request.py:362
+#: kallithea/model/pull_request.py:360
 #, python-format
 msgid "No changes found on %s %s since previous iteration."
 msgstr ""
 
-#: kallithea/model/pull_request.py:388
+#: kallithea/model/pull_request.py:386
 #, python-format
 msgid "Closed, next iteration: %s ."
 msgstr ""
 
-#: kallithea/model/scm.py:668
+#: kallithea/model/scm.py:655
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:56
+#: kallithea/model/ssh_key.py:57
 #, python-format
 msgid "SSH key %r is invalid: %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:68
+#: kallithea/model/ssh_key.py:69
 #, python-format
 msgid "SSH key %s is already used by %s"
 msgstr ""
 
-#: kallithea/model/ssh_key.py:89
+#: kallithea/model/ssh_key.py:88
 #, fuzzy, python-format
 #| msgid "Set changeset status"
-msgid "SSH key %r not found"
+msgid "SSH key with fingerprint %r found"
 msgstr "尚未有任何變更"
 
-#: kallithea/model/user.py:186
+#: kallithea/model/user.py:184
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:250
+#: kallithea/model/user.py:248
 msgid ""
 "You can't remove this user since it is crucial for the entire application"
 msgstr "您無法移除這個使用者,因為係供整個應用使用"
 
-#: kallithea/model/user.py:255
+#: kallithea/model/user.py:253
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:260
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repository groups and cannot be removed. Switch "
 "owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:267
+#: kallithea/model/user.py:265
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:361
+#: kallithea/model/user.py:359
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:406
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:407
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2325,7 +2327,7 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
 #: kallithea/templates/admin/settings/settings_global.html:50
 #: kallithea/templates/admin/settings/settings_vcs.html:66
-#: kallithea/templates/admin/settings/settings_visual.html:127
+#: kallithea/templates/admin/settings/settings_visual.html:129
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #: kallithea/templates/admin/users/user_edit_api_keys.html:73
@@ -3410,7 +3412,7 @@
 
 #: kallithea/templates/admin/settings/settings_global.html:49
 #: kallithea/templates/admin/settings/settings_vcs.html:65
-#: kallithea/templates/admin/settings/settings_visual.html:126
+#: kallithea/templates/admin/settings/settings_visual.html:128
 #, fuzzy
 msgid "Save Settings"
 msgstr "儲存設定"
@@ -3658,56 +3660,56 @@
 "@{hostname}/{repo}'."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:65
+#: kallithea/templates/admin/settings/settings_visual.html:67
 #, fuzzy
 #| msgid "repositories"
 msgid "Repository page size"
 msgstr "個版本庫"
 
-#: kallithea/templates/admin/settings/settings_visual.html:68
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid ""
 "Number of items displayed in the repository pages before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:73
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Admin page size"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:76
+#: kallithea/templates/admin/settings/settings_visual.html:78
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:81
+#: kallithea/templates/admin/settings/settings_visual.html:83
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:88
 msgid "Show public repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid "Show private repository icon on repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:95
+#: kallithea/templates/admin/settings/settings_visual.html:97
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:100
+#: kallithea/templates/admin/settings/settings_visual.html:102
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "設定"
 
-#: kallithea/templates/admin/settings/settings_visual.html:105
+#: kallithea/templates/admin/settings/settings_visual.html:107
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:109
+#: kallithea/templates/admin/settings/settings_visual.html:111
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
@@ -4349,23 +4351,23 @@
 msgid "Merge"
 msgstr "合併"
 
-#: kallithea/templates/changeset/changeset.html:96
+#: kallithea/templates/changeset/changeset.html:95
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:102
+#: kallithea/templates/changeset/changeset.html:100
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:108
+#: kallithea/templates/changeset/changeset.html:106
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:122
+#: kallithea/templates/changeset/changeset.html:120
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/changeset/changeset.html:137
 #: kallithea/templates/compare/compare_diff.html:59
 #: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
@@ -4373,7 +4375,7 @@
 msgid_plural "%s files changed"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/changeset/changeset.html:139
 #: kallithea/templates/compare/compare_diff.html:61
 #: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
@@ -4381,8 +4383,8 @@
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset.html:154
-#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/changeset/changeset.html:152
+#: kallithea/templates/changeset/changeset.html:171
 #: kallithea/templates/compare/compare_diff.html:81
 #: kallithea/templates/pullrequests/pullrequest_show.html:309
 #: kallithea/templates/pullrequests/pullrequest_show.html:333
@@ -5385,45 +5387,45 @@
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:87
-#: kallithea/templates/summary/summary.html:371
+#: kallithea/templates/summary/statistics.html:85
+#: kallithea/templates/summary/summary.html:369
 msgid "files"
 msgstr "檔案"
 
-#: kallithea/templates/summary/statistics.html:111
-#: kallithea/templates/summary/summary.html:401
+#: kallithea/templates/summary/statistics.html:109
+#: kallithea/templates/summary/summary.html:399
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:405
+#: kallithea/templates/summary/statistics.html:403
 msgid "commits"
 msgstr "遞交"
 
-#: kallithea/templates/summary/statistics.html:406
+#: kallithea/templates/summary/statistics.html:404
 msgid "files added"
 msgstr "多個檔案新增"
 
-#: kallithea/templates/summary/statistics.html:407
+#: kallithea/templates/summary/statistics.html:405
 msgid "files changed"
 msgstr "多個檔案修改"
 
+#: kallithea/templates/summary/statistics.html:406
+msgid "files removed"
+msgstr "移除多個檔案"
+
 #: kallithea/templates/summary/statistics.html:408
-msgid "files removed"
-msgstr "移除多個檔案"
-
-#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "遞交"
 
-#: kallithea/templates/summary/statistics.html:411
+#: kallithea/templates/summary/statistics.html:409
 msgid "file added"
 msgstr "檔案新增"
 
-#: kallithea/templates/summary/statistics.html:412
+#: kallithea/templates/summary/statistics.html:410
 msgid "file changed"
 msgstr "檔案修改"
 
-#: kallithea/templates/summary/statistics.html:413
+#: kallithea/templates/summary/statistics.html:411
 msgid "file removed"
 msgstr "移除檔案"
 
--- a/kallithea/lib/annotate.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/annotate.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,16 +25,15 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-import StringIO
-
 from pygments import highlight
 from pygments.formatters import HtmlFormatter
 
 from kallithea.lib.vcs.exceptions import VCSError
 from kallithea.lib.vcs.nodes import FileNode
+from kallithea.lib.vcs.utils import safe_str
 
 
-def annotate_highlight(filenode, annotate_from_changeset_func=None,
+def annotate_highlight(filenode, annotate_from_changeset_func,
         order=None, headers=None, **options):
     """
     Returns html portion containing annotated table with 3 columns: line
@@ -51,26 +50,26 @@
     """
     from kallithea.lib.pygmentsutils import get_custom_lexer
     options['linenos'] = True
-    formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
-        headers=headers,
-        annotate_from_changeset_func=annotate_from_changeset_func, **options)
+    formatter = AnnotateHtmlFormatter(filenode=filenode,
+        annotate_from_changeset_func=annotate_from_changeset_func, order=order,
+        headers=headers, **options)
     lexer = get_custom_lexer(filenode.extension) or filenode.lexer
-    highlighted = highlight(filenode.content, lexer, formatter)
+    highlighted = highlight(safe_str(filenode.content), lexer, formatter)
     return highlighted
 
 
 class AnnotateHtmlFormatter(HtmlFormatter):
 
-    def __init__(self, filenode, annotate_from_changeset_func=None,
+    def __init__(self, filenode, annotate_from_changeset_func,
             order=None, **options):
         """
-        If ``annotate_from_changeset_func`` is passed it should be a function
+        ``annotate_from_changeset_func`` must be a function
         which returns string from the given changeset. For example, we may pass
         following function as ``annotate_from_changeset_func``::
 
             def changeset_to_anchor(changeset):
                 return '<a href="/changesets/%s/">%s</a>\n' % \
-                       (changeset.id, changeset.id)
+                       (changeset.raw_id, changeset.raw_id)
 
         :param annotate_from_changeset_func: see above
         :param order: (default: ``['ls', 'annotate', 'code']``); order of
@@ -101,22 +100,13 @@
             raise VCSError("This formatter expect FileNode parameter, not %r"
                 % type(filenode))
 
-    def annotate_from_changeset(self, changeset):
-        """
-        Returns full html line for single changeset per annotated line.
-        """
-        if self.annotate_from_changeset_func:
-            return self.annotate_from_changeset_func(changeset)
-        else:
-            return ''.join((changeset.id, '\n'))
-
     def _wrap_tablelinenos(self, inner):
-        dummyoutfile = StringIO.StringIO()
+        inner_lines = []
         lncount = 0
         for t, line in inner:
             if t:
                 lncount += 1
-            dummyoutfile.write(line)
+            inner_lines.append(line)
 
         fl = self.linenostart
         mw = len(str(lncount + fl - 1))
@@ -166,7 +156,7 @@
 #        ln_ = len(ls.splitlines())
 #        if  ln_cs > ln_:
 #            annotate_changesets = annotate_changesets[:ln_ - ln_cs]
-        annotate = ''.join((self.annotate_from_changeset(el[2]())
+        annotate = ''.join((self.annotate_from_changeset_func(el[2]())
                             for el in self.filenode.annotate))
         # in case you wonder about the seemingly redundant <div> here:
         # since the content in the other cell also is wrapped in a div,
@@ -176,7 +166,7 @@
                   '<tr><td class="linenos"><div class="linenodiv"><pre>' +
                   ls + '</pre></div></td>' +
                   '<td class="code">')
-        yield 0, dummyoutfile.getvalue()
+        yield 0, ''.join(inner_lines)
         yield 0, '</td></tr></table>'
 
         '''
@@ -204,5 +194,5 @@
                   ''.join(headers_row) +
                   ''.join(body_row_start)
                   )
-        yield 0, dummyoutfile.getvalue()
+        yield 0, ''.join(inner_lines)
         yield 0, '</td></tr></table>'
--- a/kallithea/lib/app_globals.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/app_globals.py	Thu Feb 06 01:19:23 2020 +0100
@@ -39,9 +39,7 @@
         """One instance of Globals is created during application
         initialization and is available during requests via the
         'app_globals' variable
-
         """
-        self.available_permissions = None   # propagated after init_model
 
     @property
     def cache(self):
--- a/kallithea/lib/auth.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/auth.py	Thu Feb 06 01:19:23 2020 +0100
@@ -28,6 +28,7 @@
 import itertools
 import logging
 import os
+import string
 
 import ipaddr
 from decorator import decorator
@@ -41,7 +42,7 @@
 from kallithea.config.routing import url
 from kallithea.lib.caching_query import FromCache
 from kallithea.lib.utils import conditional_cache, get_repo_group_slug, get_repo_slug, get_user_group_slug
-from kallithea.lib.utils2 import safe_str, safe_unicode
+from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.model.db import (
     Permission, RepoGroup, Repository, User, UserApiKeys, UserGroup, UserGroupMember, UserGroupRepoGroupToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupUserGroupToPerm, UserIpMap, UserToPerm)
@@ -95,7 +96,7 @@
         return hashlib.sha256(password).hexdigest()
     elif is_unix:
         import bcrypt
-        return bcrypt.hashpw(safe_str(password), bcrypt.gensalt(10))
+        return ascii_str(bcrypt.hashpw(safe_bytes(password), bcrypt.gensalt(10)))
     else:
         raise Exception('Unknown or unsupported platform %s'
                         % __platform__)
@@ -109,13 +110,14 @@
     :param password: password
     :param hashed: password in hashed form
     """
-
-    if is_windows:
-        return hashlib.sha256(password).hexdigest() == hashed
+    # sha256 hashes will always be 64 hex chars
+    # bcrypt hashes will always contain $ (and be shorter)
+    if is_windows or len(hashed) == 64 and all(x in string.hexdigits for x in hashed):
+        return hashlib.sha256(safe_bytes(password)).hexdigest() == hashed
     elif is_unix:
         import bcrypt
         try:
-            return bcrypt.checkpw(safe_str(password), safe_str(hashed))
+            return bcrypt.checkpw(safe_bytes(password), ascii_bytes(hashed))
         except ValueError as e:
             # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
             log.error('error from bcrypt checking password: %s', e)
@@ -440,7 +442,7 @@
             self.is_default_user = False
         else:
             # copy non-confidential database fields from a `db.User` to this `AuthUser`.
-            for k, v in dbuser.get_dict().iteritems():
+            for k, v in dbuser.get_dict().items():
                 assert k not in ['api_keys', 'permissions']
                 setattr(self, k, v)
             self.is_default_user = dbuser.is_default_user
@@ -523,7 +525,7 @@
         """
         Returns list of repositories you're an admin of
         """
-        return [x[0] for x in self.permissions['repositories'].iteritems()
+        return [x[0] for x in self.permissions['repositories'].items()
                 if x[1] == 'repository.admin']
 
     @property
@@ -531,7 +533,7 @@
         """
         Returns list of repository groups you're an admin of
         """
-        return [x[0] for x in self.permissions['repositories_groups'].iteritems()
+        return [x[0] for x in self.permissions['repositories_groups'].items()
                 if x[1] == 'group.admin']
 
     @property
@@ -539,11 +541,11 @@
         """
         Returns list of user groups you're an admin of
         """
-        return [x[0] for x in self.permissions['user_groups'].iteritems()
+        return [x[0] for x in self.permissions['user_groups'].items()
                 if x[1] == 'usergroup.admin']
 
     def __repr__(self):
-        return "<AuthUser('id:%s[%s]')>" % (self.user_id, self.username)
+        return "<%s %s: %r>" % (self.__class__.__name__, self.user_id, self.username)
 
     def to_cookie(self):
         """ Serializes this login session to a cookie `dict`. """
@@ -594,24 +596,6 @@
         return _set or set(['0.0.0.0/0', '::/0'])
 
 
-def set_available_permissions(config):
-    """
-    This function will propagate globals with all available defined
-    permission given in db. We don't want to check each time from db for new
-    permissions since adding a new permission also requires application restart
-    ie. to decorate new views with the newly created permission
-
-    :param config: current config instance
-
-    """
-    log.info('getting information about all available permissions')
-    try:
-        all_perms = Session().query(Permission).all()
-        config['available_permissions'] = [x.permission_name for x in all_perms]
-    finally:
-        Session.remove()
-
-
 #==============================================================================
 # CHECK DECORATORS
 #==============================================================================
@@ -776,7 +760,7 @@
     def __init__(self, *required_perms):
         self.required_perms = required_perms # usually very short - a list is thus fine
 
-    def __nonzero__(self):
+    def __bool__(self):
         """ Defend against accidentally forgetting to call the object
             and instead evaluating it directly in a boolean context,
             which could have security implications.
@@ -833,10 +817,6 @@
         self.required_perms = set(perms)
 
     def __call__(self, authuser, repo_name, purpose=None):
-        # repo_name MUST be unicode, since we handle keys in ok
-        # dict by unicode
-        repo_name = safe_unicode(repo_name)
-
         try:
             ok = authuser.permissions['repositories'][repo_name] in self.required_perms
         except KeyError:
--- a/kallithea/lib/auth_modules/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/auth_modules/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -309,7 +309,7 @@
                         "a subclass of %s" % (plugin, KallitheaAuthPluginBase))
 
     plugin = pluginclass()
-    if plugin.plugin_settings.im_func != KallitheaAuthPluginBase.plugin_settings.im_func:
+    if plugin.plugin_settings.__func__ != KallitheaAuthPluginBase.plugin_settings:
         raise TypeError("Authentication class %s.KallitheaAuthPluginBase "
                         "has overridden the plugin_settings method, which is "
                         "forbidden." % plugin)
--- a/kallithea/lib/auth_modules/auth_container.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/auth_modules/auth_container.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,7 +29,7 @@
 
 from kallithea.lib import auth_modules
 from kallithea.lib.compat import hybrid_property
-from kallithea.lib.utils2 import safe_str, safe_unicode, str2bool
+from kallithea.lib.utils2 import str2bool
 from kallithea.model.db import Setting
 
 
@@ -180,7 +180,7 @@
         # only way to log in is using environ
         username = None
         if userobj:
-            username = safe_str(getattr(userobj, 'username'))
+            username = getattr(userobj, 'username')
 
         if not username:
             # we don't have any objects in DB, user doesn't exist, extract
@@ -199,8 +199,8 @@
 
         user_data = {
             'username': username,
-            'firstname': safe_unicode(firstname or username),
-            'lastname': safe_unicode(lastname or ''),
+            'firstname': firstname or username,
+            'lastname': lastname or '',
             'groups': [],
             'email': email or '',
             'admin': admin or False,
--- a/kallithea/lib/auth_modules/auth_crowd.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/auth_modules/auth_crowd.py	Thu Feb 06 01:19:23 2020 +0100
@@ -28,10 +28,12 @@
 
 import base64
 import logging
-import urllib2
+import urllib.parse
+import urllib.request
 
-from kallithea.lib import auth_modules
-from kallithea.lib.compat import formatted_json, hybrid_property, json
+from kallithea.lib import auth_modules, ext_json
+from kallithea.lib.compat import hybrid_property
+from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes
 
 
 log = logging.getLogger(__name__)
@@ -71,10 +73,10 @@
         self._make_opener()
 
     def _make_opener(self):
-        mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+        mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
         mgr.add_password(None, self._uri, self.user, self.passwd)
-        handler = urllib2.HTTPBasicAuthHandler(mgr)
-        self.opener = urllib2.build_opener(handler)
+        handler = urllib.request.HTTPBasicAuthHandler(mgr)
+        self.opener = urllib.request.build_opener(handler)
 
     def _request(self, url, body=None, headers=None,
                  method=None, noformat=False,
@@ -82,14 +84,12 @@
         _headers = {"Content-type": "application/json",
                     "Accept": "application/json"}
         if self.user and self.passwd:
-            authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
+            authstring = ascii_str(base64.b64encode(safe_bytes("%s:%s" % (self.user, self.passwd))))
             _headers["Authorization"] = "Basic %s" % authstring
         if headers:
             _headers.update(headers)
-        log.debug("Sent crowd: \n%s",
-                  formatted_json({"url": url, "body": body,
-                                           "headers": _headers}))
-        req = urllib2.Request(url, body, _headers)
+        log.debug("Sent to crowd at %s:\nHeaders: %s\nBody:\n%s", url, _headers, body)
+        req = urllib.request.Request(url, body, _headers)
         if method:
             req.get_method = lambda: method
 
@@ -103,7 +103,7 @@
                 rval["status"] = True
                 rval["error"] = "Response body was empty"
             elif not noformat:
-                rval = json.loads(msg)
+                rval = ext_json.loads(msg)
                 rval["status"] = True
             else:
                 rval = "".join(rdoc.readlines())
@@ -120,14 +120,14 @@
         """Authenticate a user against crowd. Returns brief information about
         the user."""
         url = ("%s/rest/usermanagement/%s/authentication?username=%s"
-               % (self._uri, self._version, urllib2.quote(username)))
-        body = json.dumps({"value": password})
+               % (self._uri, self._version, urllib.parse.quote(username)))
+        body = ascii_bytes(ext_json.dumps({"value": password}))
         return self._request(url, body)
 
     def user_groups(self, username):
         """Retrieve a list of groups to which this user belongs."""
         url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
-               % (self._uri, self._version, urllib2.quote(username)))
+               % (self._uri, self._version, urllib.parse.quote(username)))
         return self._request(url)
 
 
@@ -209,11 +209,11 @@
             log.debug('Empty username or password skipping...')
             return None
 
-        log.debug("Crowd settings: \n%s", formatted_json(settings))
+        log.debug("Crowd settings: %s", settings)
         server = CrowdServer(**settings)
         server.set_credentials(settings["app_name"], settings["app_password"])
         crowd_user = server.user_auth(username, password)
-        log.debug("Crowd returned: \n%s", formatted_json(crowd_user))
+        log.debug("Crowd returned: %s", crowd_user)
         if not crowd_user["status"]:
             log.error('Crowd authentication as %s returned no status', username)
             return None
@@ -223,7 +223,7 @@
             return None
 
         res = server.user_groups(crowd_user["name"])
-        log.debug("Crowd groups: \n%s", formatted_json(res))
+        log.debug("Crowd groups: %s", res)
         crowd_user["groups"] = [x["name"] for x in res["groups"]]
 
         # old attrs fetched from Kallithea database
@@ -246,7 +246,7 @@
         for group in settings["admin_groups"].split(","):
             if group in user_data["groups"]:
                 user_data["admin"] = True
-        log.debug("Final crowd user object: \n%s", formatted_json(user_data))
+        log.debug("Final crowd user object: %s", user_data)
         log.info('user %s authenticated correctly', user_data['username'])
         return user_data
 
--- a/kallithea/lib/auth_modules/auth_internal.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/auth_modules/auth_internal.py	Thu Feb 06 01:19:23 2020 +0100
@@ -30,7 +30,6 @@
 
 from kallithea.lib import auth_modules
 from kallithea.lib.compat import formatted_json, hybrid_property
-from kallithea.model.db import User
 
 
 log = logging.getLogger(__name__)
--- a/kallithea/lib/auth_modules/auth_ldap.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/auth_modules/auth_ldap.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,7 +31,6 @@
 from kallithea.lib import auth_modules
 from kallithea.lib.compat import hybrid_property
 from kallithea.lib.exceptions import LdapConnectionError, LdapImportError, LdapPasswordError, LdapUsernameError
-from kallithea.lib.utils2 import safe_str, safe_unicode
 
 
 log = logging.getLogger(__name__)
@@ -70,11 +69,11 @@
                             port)
             for host in server.split(',')))
 
-        self.LDAP_BIND_DN = safe_str(bind_dn)
-        self.LDAP_BIND_PASS = safe_str(bind_pass)
+        self.LDAP_BIND_DN = bind_dn
+        self.LDAP_BIND_PASS = bind_pass
 
-        self.BASE_DN = safe_str(base_dn)
-        self.LDAP_FILTER = safe_str(ldap_filter)
+        self.BASE_DN = base_dn
+        self.LDAP_FILTER = ldap_filter
         self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
         self.attr_login = attr_login
 
@@ -139,7 +138,7 @@
 
                 try:
                     log.debug('Trying simple bind with %s', dn)
-                    server.simple_bind_s(dn, safe_str(password))
+                    server.simple_bind_s(dn, password)
                     results = server.search_ext_s(dn, ldap.SCOPE_BASE,
                                                   '(objectClass=*)')
                     if len(results) == 1:
@@ -338,8 +337,8 @@
 
             user_data = {
                 'username': username,
-                'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
-                'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
+                'firstname': get_ldap_attr('attr_firstname') or firstname,
+                'lastname': get_ldap_attr('attr_lastname') or lastname,
                 'groups': [],
                 'email': get_ldap_attr('attr_email') or email,
                 'admin': admin,
--- a/kallithea/lib/base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -28,9 +28,9 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
+import base64
 import datetime
 import logging
-import time
 import traceback
 import warnings
 
@@ -45,12 +45,11 @@
 
 from kallithea import BACKENDS, __version__
 from kallithea.config.routing import url
-from kallithea.lib import auth_modules
+from kallithea.lib import auth_modules, ext_json
 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
-from kallithea.lib.compat import json
 from kallithea.lib.exceptions import UserCreationError
 from kallithea.lib.utils import get_repo_slug, is_valid_repo
-from kallithea.lib.utils2 import AttributeDict, safe_int, safe_str, safe_unicode, set_hook_environment, str2bool
+from kallithea.lib.utils2 import AttributeDict, ascii_bytes, safe_int, safe_str, set_hook_environment, str2bool
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError
 from kallithea.model import meta
 from kallithea.model.db import PullRequest, Repository, Setting, User
@@ -97,12 +96,18 @@
     return _filter_proxy(ip)
 
 
-def _get_access_path(environ):
-    """Return PATH_INFO from environ ... using tg.original_request if available."""
+def get_path_info(environ):
+    """Return PATH_INFO from environ ... using tg.original_request if available.
+
+    In Python 3 WSGI, PATH_INFO is a unicode str, but kind of contains encoded
+    bytes. The code points are guaranteed to only use the lower 8 bit bits, and
+    encoding the string with the 1:1 encoding latin1 will give the
+    corresponding byte string ... which then can be decoded to proper unicode.
+    """
     org_req = environ.get('tg.original_request')
     if org_req is not None:
         environ = org_req.environ
-    return environ.get('PATH_INFO')
+    return safe_str(environ['PATH_INFO'].encode('latin1'))
 
 
 def log_in_user(user, remember, is_external_auth, ip_addr):
@@ -172,7 +177,7 @@
         (authmeth, auth) = authorization.split(' ', 1)
         if 'basic' != authmeth.lower():
             return self.build_authentication(environ)
-        auth = auth.strip().decode('base64')
+        auth = safe_str(base64.b64decode(auth.strip()))
         _parts = auth.split(':', 1)
         if len(_parts) == 2:
             username, password = _parts
@@ -242,7 +247,7 @@
 
         # If not authenticated by the container, running basic auth
         if not username:
-            self.authenticate.realm = safe_str(self.config['realm'])
+            self.authenticate.realm = self.config['realm']
             result = self.authenticate(environ)
             if isinstance(result, str):
                 paste.httpheaders.AUTH_TYPE.update(environ, 'basic')
@@ -300,7 +305,6 @@
         return _get_ip_addr(environ)
 
     def __call__(self, environ, start_response):
-        start = time.time()
         try:
             # try parsing a request for this VCS - if it fails, call the wrapped app
             parsed_request = self.parse_request(environ)
@@ -334,7 +338,7 @@
 
             try:
                 log.info('%s action on %s repo "%s" by "%s" from %s',
-                         parsed_request.action, self.scm_alias, parsed_request.repo_name, safe_str(user.username), ip_addr)
+                         parsed_request.action, self.scm_alias, parsed_request.repo_name, user.username, ip_addr)
                 app = self._make_app(parsed_request)
                 return app(environ, start_response)
             except Exception:
@@ -343,10 +347,6 @@
 
         except webob.exc.HTTPException as e:
             return e(environ, start_response)
-        finally:
-            log_ = logging.getLogger('kallithea.' + self.__class__.__name__)
-            log_.debug('Request time: %.3fs', time.time() - start)
-            meta.Session.remove()
 
 
 class BaseController(TGController):
@@ -413,7 +413,7 @@
         # END CONFIG VARS
 
         c.repo_name = get_repo_slug(request)  # can be empty
-        c.backends = BACKENDS.keys()
+        c.backends = list(BACKENDS)
 
         self.cut_off_limit = safe_int(config.get('cut_off_limit'))
 
@@ -531,7 +531,7 @@
 
             log.info('IP: %s User: %s accessed %s',
                 request.ip_addr, request.authuser,
-                safe_unicode(_get_access_path(environ)),
+                get_path_info(environ),
             )
             return super(BaseController, self).__call__(environ, context)
         except webob.exc.HTTPException as e:
@@ -558,7 +558,7 @@
                 return
 
             log.debug('Found repository in database %s with state `%s`',
-                      safe_unicode(_dbr), safe_unicode(_dbr.repo_state))
+                      _dbr, _dbr.repo_state)
             route = getattr(request.environ.get('routes.route'), 'name', '')
 
             # allow to delete repos that are somehow damages in filesystem
@@ -608,7 +608,7 @@
             raise webob.exc.HTTPNotFound()
         except RepositoryError as e:
             log.error(traceback.format_exc())
-            h.flash(safe_str(e), category='error')
+            h.flash(e, category='error')
             raise webob.exc.HTTPBadRequest()
 
 
@@ -634,7 +634,7 @@
         warnings.warn(msg, Warning, 2)
         log.warning(msg)
     log.debug("Returning JSON wrapped action output")
-    return json.dumps(data, encoding='utf-8')
+    return ascii_bytes(ext_json.dumps(data))
 
 @decorator.decorator
 def IfSshEnabled(func, *args, **kwargs):
--- a/kallithea/lib/caching_query.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/caching_query.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,3 +1,5 @@
+# apparently based on https://github.com/sqlalchemy/sqlalchemy/blob/rel_0_7/examples/beaker_caching/caching_query.py
+
 """caching_query.py
 
 Represent persistence structures which allow the usage of
@@ -22,8 +24,6 @@
 from sqlalchemy.orm.query import Query
 from sqlalchemy.sql import visitors
 
-from kallithea.lib.utils2 import safe_str
-
 
 class CachingQuery(Query):
     """A Query subclass which optionally loads full results from a Beaker
@@ -136,11 +136,10 @@
 
     if cache_key is None:
         # cache key - the value arguments from this query's parameters.
-        args = [safe_str(x) for x in _params_from_query(query)]
-        args.extend(filter(lambda k: k not in ['None', None, u'None'],
-                           [str(query._limit), str(query._offset)]))
-
-        cache_key = " ".join(args)
+        args = _params_from_query(query)
+        args.append(query._limit)
+        args.append(query._offset)
+        cache_key = " ".join(str(x) for x in args)
 
     if cache_key is None:
         raise Exception('Cache key cannot be None')
@@ -174,7 +173,7 @@
                         "for region %r namespace %r" %
                         (region, namespace)
                     )
-    query._cache_parameters = region, safe_str(namespace), cache_key
+    query._cache_parameters = region, namespace, cache_key
 
 
 class FromCache(MapperOption):
--- a/kallithea/lib/celerylib/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/celerylib/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -35,7 +35,7 @@
 
 from kallithea import CELERY_EAGER, CELERY_ON
 from kallithea.lib.pidlock import DaemonLock, LockHeld
-from kallithea.lib.utils2 import safe_str
+from kallithea.lib.utils2 import safe_bytes
 from kallithea.model import meta
 
 
@@ -95,7 +95,7 @@
     func_name = str(func.__name__) if hasattr(func, '__name__') else str(func)
 
     lockkey = 'task_%s.lock' % \
-        md5(func_name + '-' + '-'.join(map(safe_str, params))).hexdigest()
+        md5(safe_bytes(func_name + '-' + '-'.join(str(x) for x in params))).hexdigest()
     return lockkey
 
 
--- a/kallithea/lib/celerylib/tasks.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/celerylib/tasks.py	Thu Feb 06 01:19:23 2020 +0100
@@ -26,9 +26,9 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
+import email.utils
 import logging
 import os
-import rfc822
 import traceback
 from collections import OrderedDict
 from operator import itemgetter
@@ -37,13 +37,12 @@
 from tg import config
 
 from kallithea import CELERY_ON
-from kallithea.lib import celerylib
-from kallithea.lib.compat import json
+from kallithea.lib import celerylib, ext_json
 from kallithea.lib.helpers import person
 from kallithea.lib.hooks import log_create_repository
 from kallithea.lib.rcmail.smtp_mailer import SmtpMailer
 from kallithea.lib.utils import action_logger
-from kallithea.lib.utils2 import str2bool
+from kallithea.lib.utils2 import ascii_bytes, str2bool
 from kallithea.lib.vcs.utils import author_email
 from kallithea.model.db import RepoGroup, Repository, Statistics, User
 
@@ -118,22 +117,21 @@
             return True
 
         if cur_stats:
-            commits_by_day_aggregate = OrderedDict(json.loads(
+            commits_by_day_aggregate = OrderedDict(ext_json.loads(
                                         cur_stats.commit_activity_combined))
-            co_day_auth_aggr = json.loads(cur_stats.commit_activity)
+            co_day_auth_aggr = ext_json.loads(cur_stats.commit_activity)
 
         log.debug('starting parsing %s', parse_limit)
-        lmktime = mktime
 
-        last_rev = last_rev + 1 if last_rev >= 0 else 0
+        last_rev = last_rev + 1 if last_rev and last_rev >= 0 else 0
         log.debug('Getting revisions from %s to %s',
              last_rev, last_rev + parse_limit
         )
         for cs in repo[last_rev:last_rev + parse_limit]:
             log.debug('parsing %s', cs)
             last_cs = cs  # remember last parsed changeset
-            k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
-                          cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
+            tt = cs.date.timetuple()
+            k = mktime(tt[:3] + (0, 0, 0, 0, 0, 0))
 
             if akc(cs.author) in co_day_auth_aggr:
                 try:
@@ -143,8 +141,7 @@
                 except ValueError:
                     time_pos = None
 
-                if time_pos >= 0 and time_pos is not None:
-
+                if time_pos is not None and time_pos >= 0:
                     datadict = \
                         co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
 
@@ -195,8 +192,8 @@
             }
 
         stats = cur_stats if cur_stats else Statistics()
-        stats.commit_activity = json.dumps(co_day_auth_aggr)
-        stats.commit_activity_combined = json.dumps(overview_data)
+        stats.commit_activity = ascii_bytes(ext_json.dumps(co_day_auth_aggr))
+        stats.commit_activity_combined = ascii_bytes(ext_json.dumps(overview_data))
 
         log.debug('last revision %s', last_rev)
         leftovers = len(repo.revisions[last_rev:])
@@ -204,7 +201,7 @@
 
         if last_rev == 0 or leftovers < parse_limit:
             log.debug('getting code trending stats')
-            stats.languages = json.dumps(__get_codes_stats(repo_name))
+            stats.languages = ascii_bytes(ext_json.dumps(__get_codes_stats(repo_name)))
 
         try:
             stats.repository = dbrepo
@@ -282,7 +279,7 @@
         # extract the e-mail address.
         envelope_addr = author_email(envelope_from)
         headers['From'] = '"%s" <%s>' % (
-            rfc822.quote('%s (no-reply)' % author.full_name_or_username),
+            email.utils.quote('%s (no-reply)' % author.full_name_or_username),
             envelope_addr)
 
     user = email_config.get('smtp_username')
@@ -489,7 +486,7 @@
     for _topnode, _dirnodes, filenodes in tip.walk('/'):
         for filenode in filenodes:
             ext = filenode.extension.lower()
-            if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not filenode.is_binary:
+            if ext in LANGUAGES_EXTENSIONS_MAP and not filenode.is_binary:
                 if ext in code_stats:
                     code_stats[ext] += 1
                 else:
--- a/kallithea/lib/celerypylons/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/celerypylons/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -39,7 +39,7 @@
         celery_key = config_key.replace('.', '_').upper()
         if celery_key.split('_', 1)[0] not in PREFIXES:
             continue
-        if not isinstance(config_value, basestring):
+        if not isinstance(config_value, str):
             continue
         if celery_key in LIST_PARAMS:
             celery_value = config_value.split()
@@ -53,6 +53,8 @@
     return celery_config
 
 
-# Create celery app from the TurboGears configuration file
-app = celery.Celery()
-app.config_from_object(celery_config(tg.config))
+def make_app():
+    """Create celery app from the TurboGears configuration file"""
+    app = celery.Celery()
+    app.config_from_object(celery_config(tg.config))
+    return app
--- a/kallithea/lib/colored_formatter.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/colored_formatter.py	Thu Feb 06 01:19:23 2020 +0100
@@ -15,7 +15,7 @@
 import logging
 
 
-BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(30, 38)
 
 # Sequences
 RESET_SEQ = "\033[0m"
--- a/kallithea/lib/compat.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/compat.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,7 +29,6 @@
 
 import functools
 import os
-import sys
 
 #==============================================================================
 # Hybrid property/method
@@ -43,15 +42,10 @@
 #==============================================================================
 # json
 #==============================================================================
-from kallithea.lib.ext_json import json
+from kallithea.lib import ext_json
 
 
-# alias for formatted json
-formatted_json = functools.partial(json.dumps, indent=4, sort_keys=True)
-
-
-
-
+formatted_json = functools.partial(ext_json.dumps, indent=4, sort_keys=True)
 
 
 #==============================================================================
--- a/kallithea/lib/db_manage.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/db_manage.py	Thu Feb 06 01:19:23 2020 +0100
@@ -189,7 +189,7 @@
 
                 return password
             if username is None:
-                username = raw_input('Specify admin username:')
+                username = input('Specify admin username:')
             if password is None:
                 password = get_password()
                 if not password:
@@ -198,7 +198,7 @@
                     if not password:
                         sys.exit()
             if email is None:
-                email = raw_input('Specify admin email:')
+                email = input('Specify admin email:')
             self.create_user(username, password, email, True)
         else:
             log.info('creating admin and regular test users')
@@ -294,7 +294,7 @@
         if _path is not None:
             path = _path
         elif not self.tests and not test_repo_path:
-            path = raw_input(
+            path = input(
                  'Enter a valid absolute path to store repositories. '
                  'All repositories in that path will be added automatically:'
             )
--- a/kallithea/lib/diffs.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/diffs.py	Thu Feb 06 01:19:23 2020 +0100
@@ -32,7 +32,7 @@
 from tg.i18n import ugettext as _
 
 from kallithea.lib import helpers as h
-from kallithea.lib.utils2 import safe_unicode
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.exceptions import VCSError
 from kallithea.lib.vcs.nodes import FileNode, SubModuleNode
@@ -216,8 +216,7 @@
         stats = (0, 0)
 
     if not html_diff:
-        submodules = filter(lambda o: isinstance(o, SubModuleNode),
-                            [filenode_new, filenode_old])
+        submodules = [o for o in [filenode_new, filenode_old] if isinstance(o, SubModuleNode)]
         if submodules:
             html_diff = wrap_to_table(h.escape('Submodule %r' % submodules[0]))
         else:
@@ -235,10 +234,9 @@
     """
     # make sure we pass in default context
     context = context or 3
-    submodules = filter(lambda o: isinstance(o, SubModuleNode),
-                        [filenode_new, filenode_old])
+    submodules = [o for o in [filenode_new, filenode_old] if isinstance(o, SubModuleNode)]
     if submodules:
-        return ''
+        return b''
 
     for filenode in (filenode_old, filenode_new):
         if not isinstance(filenode, FileNode):
@@ -263,7 +261,7 @@
                                      ignore_whitespace=ignore_whitespace, context=context)
     except MemoryError:
         h.flash('MemoryError: Diff is too big', category='error')
-        return ''
+        return b''
 
 
 NEW_FILENODE = 1
@@ -281,7 +279,7 @@
     mentioned in the diff together with a dict of meta information that
     can be used to render it in a HTML template.
     """
-    _diff_git_re = re.compile('^diff --git', re.MULTILINE)
+    _diff_git_re = re.compile(b'^diff --git', re.MULTILINE)
 
     def __init__(self, diff, vcs='hg', diff_limit=None, inline_diff=True):
         """
@@ -291,10 +289,10 @@
             based on that parameter cut off will be triggered, set to None
             to show full diff
         """
-        if not isinstance(diff, basestring):
-            raise Exception('Diff must be a basestring got %s instead' % type(diff))
+        if not isinstance(diff, bytes):
+            raise Exception('Diff must be bytes - got %s' % type(diff))
 
-        self._diff = diff
+        self._diff = memoryview(diff)
         self.adds = 0
         self.removes = 0
         self.diff_limit = diff_limit
@@ -317,7 +315,7 @@
                 self.limited_diff = True
                 continue
 
-            head, diff_lines = _get_header(self.vcs, buffer(self._diff, start, end - start))
+            head, diff_lines = _get_header(self.vcs, self._diff[start:end])
 
             op = None
             stats = {
@@ -399,7 +397,7 @@
                 'new_lineno': '',
                 'action':     'context',
                 'line':       msg,
-                } for _op, msg in stats['ops'].iteritems()
+                } for _op, msg in stats['ops'].items()
                   if _op not in [MOD_FILENODE]])
 
             _files.append({
@@ -420,22 +418,22 @@
             for chunk in diff_data['chunks']:
                 lineiter = iter(chunk)
                 try:
-                    peekline = lineiter.next()
+                    peekline = next(lineiter)
                     while True:
                         # find a first del line
                         while peekline['action'] != 'del':
-                            peekline = lineiter.next()
+                            peekline = next(lineiter)
                         delline = peekline
-                        peekline = lineiter.next()
+                        peekline = next(lineiter)
                         # if not followed by add, eat all following del lines
                         if peekline['action'] != 'add':
                             while peekline['action'] == 'del':
-                                peekline = lineiter.next()
+                                peekline = next(lineiter)
                             continue
                         # found an add - make sure it is the only one
                         addline = peekline
                         try:
-                            peekline = lineiter.next()
+                            peekline = next(lineiter)
                         except StopIteration:
                             # add was last line - ok
                             _highlight_inline_diff(delline, addline)
@@ -479,10 +477,10 @@
             return ' <i></i>'
         assert False
 
-    return _escape_re.sub(substitute, safe_unicode(string))
+    return _escape_re.sub(substitute, safe_str(string))
 
 
-_git_header_re = re.compile(r"""
+_git_header_re = re.compile(br"""
     ^diff[ ]--git[ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
     (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
        ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
@@ -499,7 +497,7 @@
 """, re.VERBOSE | re.MULTILINE)
 
 
-_hg_header_re = re.compile(r"""
+_hg_header_re = re.compile(br"""
     ^diff[ ]--git[ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
     (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
        ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
@@ -518,6 +516,9 @@
 """, re.VERBOSE | re.MULTILINE)
 
 
+_header_next_check = re.compile(br'''(?!@)(?!literal )(?!delta )''')
+
+
 def _get_header(vcs, diff_chunk):
     """
     Parses a Git diff for a single file (header and chunks) and returns a tuple with:
@@ -537,11 +538,11 @@
         match = _hg_header_re.match(diff_chunk)
     if match is None:
         raise Exception('diff not recognized as valid %s diff' % vcs)
-    meta_info = match.groupdict()
+    meta_info = {k: None if v is None else safe_str(v) for k, v in match.groupdict().items()}
     rest = diff_chunk[match.end():]
-    if rest and not rest.startswith('@') and not rest.startswith('literal ') and not rest.startswith('delta '):
-        raise Exception('cannot parse %s diff header: %r followed by %r' % (vcs, diff_chunk[:match.end()], rest[:1000]))
-    diff_lines = (_escaper(m.group(0)) for m in re.finditer(r'.*\n|.+$', rest)) # don't split on \r as str.splitlines do
+    if rest and _header_next_check.match(rest):
+        raise Exception('cannot parse %s diff header: %r followed by %r' % (vcs, safe_str(bytes(diff_chunk[:match.end()])), safe_str(bytes(rest[:1000]))))
+    diff_lines = (_escaper(m.group(0)) for m in re.finditer(br'.*\n|.+$', rest)) # don't split on \r as str.splitlines do
     return meta_info, diff_lines
 
 
@@ -557,9 +558,9 @@
     added = deleted = 0
     old_line = old_end = new_line = new_end = None
 
+    chunks = []
     try:
-        chunks = []
-        line = diff_lines.next()
+        line = next(diff_lines)
 
         while True:
             lines = []
@@ -590,7 +591,7 @@
                         'line':       line,
                     })
 
-            line = diff_lines.next()
+            line = next(diff_lines)
 
             while old_line < old_end or new_line < new_end:
                 if not line:
@@ -623,7 +624,7 @@
                         'line':         line[1:],
                     })
 
-                line = diff_lines.next()
+                line = next(diff_lines)
 
                 if _newline_marker.match(line):
                     # we need to append to lines, since this is not
@@ -634,7 +635,7 @@
                         'action':       'context',
                         'line':         line,
                     })
-                    line = diff_lines.next()
+                    line = next(diff_lines)
             if old_line > old_end:
                 raise Exception('error parsing diff - more than %s "-" lines at -%s+%s' % (old_end, old_line, new_line))
             if new_line > new_end:
--- a/kallithea/lib/ext_json.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/ext_json.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,16 +1,16 @@
 """
-Extended JSON encoder for json
+Extended JSON encoder with support for more data types
 
-json.org does not specify how date time can be represented - monkeypatch it to do something.
+json.org does not specify how date time can be represented - just encode it somehow and ignore decoding ...
 """
 
 import datetime
 import decimal
 import functools
-import json  # is re-exported after monkey patching
+import json
 
 
-__all__ = ['json']
+__all__ = ['dumps', 'dump', 'load', 'loads']
 
 
 def _is_tz_aware(value):
@@ -70,10 +70,12 @@
         try:
             return _obj_dump(obj)
         except NotImplementedError:
-            pass
+            pass  # quiet skipping of unsupported types!
         raise TypeError("%r is not JSON serializable" % (obj,))
 
 
-# monkey-patch and export JSON encoder to use custom encoding method
-json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
-json.dump = functools.partial(json.dump, cls=ExtendedEncoder)
+dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
+dump = functools.partial(json.dump, cls=ExtendedEncoder)
+# No special support for loading these types back!!!
+load = json.load
+loads = json.loads
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/feeds.py	Thu Feb 06 01:19:23 2020 +0100
@@ -0,0 +1,152 @@
+# -*- 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.feeds
+~~~~~~~~~~~~~~~~~~~
+
+Shared code for providing RSS and ATOM feeds.
+"""
+
+import datetime
+import re
+
+import mako.template
+
+
+language = 'en-us'
+ttl = "5"
+
+
+# From ``django.utils.feedgenerator`` via webhelpers.feedgenerator
+def rfc2822_date(date):
+    # We do this ourselves to be timezone aware, email.Utils is not tz aware.
+    if getattr(date, "tzinfo", False):
+        time_str = date.strftime('%a, %d %b %Y %H:%M:%S ')
+        offset = date.tzinfo.utcoffset(date)
+        timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
+        hour, minute = divmod(timezone, 60)
+        return time_str + "%+03d%02d" % (hour, minute)
+    else:
+        return date.strftime('%a, %d %b %Y %H:%M:%S -0000')
+
+# From ``django.utils.feedgenerator`` via webhelpers.feedgenerator
+def rfc3339_date(date):
+    if getattr(date, "tzinfo", False):
+        time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
+        offset = date.tzinfo.utcoffset(date)
+        timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
+        hour, minute = divmod(timezone, 60)
+        return time_str + "%+03d:%02d" % (hour, minute)
+    else:
+        return date.strftime('%Y-%m-%dT%H:%M:%SZ')
+
+# From ``django.utils.feedgenerator`` via webhelpers.feedgenerator
+def get_tag_uri(url, date):
+    "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
+    tag = re.sub('^http://', '', url)
+    if date is not None:
+        tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
+    tag = re.sub('#', '/', tag)
+    return u'tag:' + tag
+
+
+class Attributes(object):
+    """Simple namespace for attribute dict access in mako and elsewhere"""
+    def __init__(self, a_dict):
+        self.__dict__ = a_dict
+
+
+class _Feeder(object):
+
+    content_type = None
+    template = None  # subclass must provide a mako.template.Template
+
+    @classmethod
+    def render(cls, header, entries):
+        try:
+            latest_pubdate = max(
+                pubdate for pubdate in (e.get('pubdate') for e in entries)
+                if pubdate
+            )
+        except ValueError:  # max() arg is an empty sequence ... or worse
+            latest_pubdate = datetime.datetime.now()
+
+        return cls.template.render(
+            language=language,
+            ttl=ttl,  # rss only
+            latest_pubdate=latest_pubdate,
+            rfc2822_date=rfc2822_date,  # for RSS
+            rfc3339_date=rfc3339_date,  # for Atom
+            get_tag_uri=get_tag_uri,
+            entries=[Attributes(e) for e in entries],
+            **header
+        )
+
+
+class AtomFeed(_Feeder):
+
+    content_type = 'application/atom+xml'
+
+    template = mako.template.Template('''\
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${language}">
+  <title>${title}</title>
+  <link href="${link}" rel="alternate"></link>
+  <id>${link}</id>
+  <updated>${rfc3339_date(latest_pubdate)}</updated>
+  % for entry in entries:
+  <entry>
+    <title>${entry.title}</title>
+    <link href="${entry.link}" rel="alternate"></link>
+    <updated>${rfc3339_date(entry.pubdate)}</updated>
+    <published>${rfc3339_date(entry.pubdate)}</published>
+    <author>
+      <name>${entry.author_name}</name>
+      <email>${entry.author_email}</email>
+    </author>
+    <id>${get_tag_uri(entry.link, entry.pubdate)}</id>
+    <summary type="html">${entry.description}</summary>
+  </entry>
+  % endfor
+</feed>
+''', default_filters=['x'], output_encoding='utf-8', encoding_errors='replace')
+
+
+class RssFeed(_Feeder):
+
+    content_type = 'application/rss+xml'
+
+    template = mako.template.Template('''\
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0">
+  <channel>
+    <title>${title}</title>
+    <link>${link}</link>
+    <description>${description}</description>
+    <language>${language}</language>
+    <lastBuildDate>${rfc2822_date(latest_pubdate)}</lastBuildDate>
+    <ttl>${ttl}</ttl>
+    % for entry in entries:
+    <item>
+      <title>${entry.title}</title>
+      <link>${entry.link}</link>
+      <description>${entry.description}</description>
+      <author>${entry.author_email} (${entry.author_name})</author>
+      <pubDate>${rfc2822_date(entry.pubdate)}</pubDate>
+    </item>
+    % endfor
+  </channel>
+</rss>
+''', default_filters=['x'], output_encoding='utf-8', encoding_errors='replace')
--- a/kallithea/lib/helpers.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/helpers.py	Thu Feb 06 01:19:23 2020 +0100
@@ -22,9 +22,8 @@
 import logging
 import random
 import re
-import StringIO
 import textwrap
-import urlparse
+import urllib.parse
 
 from beaker.cache import cache_region
 from pygments import highlight as code_highlight
@@ -49,7 +48,7 @@
 from kallithea.lib.pygmentsutils import get_custom_lexer
 from kallithea.lib.utils2 import MENTIONS_REGEX, AttributeDict
 from kallithea.lib.utils2 import age as _age
-from kallithea.lib.utils2 import credentials_filter, safe_int, safe_str, safe_unicode, str2bool, time_to_datetime
+from kallithea.lib.utils2 import credentials_filter, safe_bytes, safe_int, safe_str, str2bool, time_to_datetime
 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
 #==============================================================================
@@ -167,7 +166,7 @@
         for x in option_list:
             if isinstance(x, tuple) and len(x) == 2:
                 value, label = x
-            elif isinstance(x, basestring):
+            elif isinstance(x, str):
                 value = label = x
             else:
                 log.error('invalid select option %r', x)
@@ -177,7 +176,7 @@
                 for x in value:
                     if isinstance(x, tuple) and len(x) == 2:
                         group_value, group_label = x
-                    elif isinstance(x, basestring):
+                    elif isinstance(x, str):
                         group_value = group_label = x
                     else:
                         log.error('invalid select option %r', x)
@@ -200,14 +199,12 @@
     :param path:
     """
 
-    return 'C-%s-%s' % (short_id(raw_id), hashlib.md5(safe_str(path)).hexdigest()[:12])
+    return 'C-%s-%s' % (short_id(raw_id), hashlib.md5(safe_bytes(path)).hexdigest()[:12])
 
 
 class _FilesBreadCrumbs(object):
 
     def __call__(self, repo_name, rev, paths):
-        if isinstance(paths, str):
-            paths = safe_unicode(paths)
         url_l = [link_to(repo_name, url('files_home',
                                         repo_name=repo_name,
                                         revision=rev, f_path=''),
@@ -246,12 +243,12 @@
             yield i, t
 
     def _wrap_tablelinenos(self, inner):
-        dummyoutfile = StringIO.StringIO()
+        inner_lines = []
         lncount = 0
         for t, line in inner:
             if t:
                 lncount += 1
-            dummyoutfile.write(line)
+            inner_lines.append(line)
 
         fl = self.linenostart
         mw = len(str(lncount + fl - 1))
@@ -304,7 +301,7 @@
                       '<tr><td class="linenos"><div class="linenodiv">'
                       '<pre>' + ls + '</pre></div></td>'
                       '<td id="hlcode" class="code">')
-        yield 0, dummyoutfile.getvalue()
+        yield 0, ''.join(inner_lines)
         yield 0, '</td></tr></table>'
 
 
@@ -331,7 +328,48 @@
     """
     lexer = get_custom_lexer(filenode.extension) or filenode.lexer
     return literal(markup_whitespace(
-        code_highlight(filenode.content, lexer, CodeHtmlFormatter(**kwargs))))
+        code_highlight(safe_str(filenode.content), lexer, CodeHtmlFormatter(**kwargs))))
+
+
+def hsv_to_rgb(h, s, v):
+    if s == 0.0:
+        return v, v, v
+    i = int(h * 6.0)  # XXX assume int() truncates!
+    f = (h * 6.0) - i
+    p = v * (1.0 - s)
+    q = v * (1.0 - s * f)
+    t = v * (1.0 - s * (1.0 - f))
+    i = i % 6
+    if i == 0:
+        return v, t, p
+    if i == 1:
+        return q, v, p
+    if i == 2:
+        return p, v, t
+    if i == 3:
+        return p, q, v
+    if i == 4:
+        return t, p, v
+    if i == 5:
+        return v, p, q
+
+
+def gen_color(n=10000):
+    """generator for getting n of evenly distributed colors using
+    hsv color and golden ratio. It always return same order of colors
+
+    :returns: RGB tuple
+    """
+
+    golden_ratio = 0.618033988749895
+    h = 0.22717784590367374
+
+    for _unused in range(n):
+        h += golden_ratio
+        h %= 1
+        HSV_tuple = [h, 0.95, 0.95]
+        RGB_tuple = hsv_to_rgb(*HSV_tuple)
+        yield [str(int(x * 256)) for x in RGB_tuple]
 
 
 def pygmentize_annotation(repo_name, filenode, **kwargs):
@@ -340,82 +378,38 @@
 
     :param filenode:
     """
-
+    cgenerator = gen_color()
     color_dict = {}
 
-    def gen_color(n=10000):
-        """generator for getting n of evenly distributed colors using
-        hsv color and golden ratio. It always return same order of colors
-
-        :returns: RGB tuple
-        """
-
-        def hsv_to_rgb(h, s, v):
-            if s == 0.0:
-                return v, v, v
-            i = int(h * 6.0)  # XXX assume int() truncates!
-            f = (h * 6.0) - i
-            p = v * (1.0 - s)
-            q = v * (1.0 - s * f)
-            t = v * (1.0 - s * (1.0 - f))
-            i = i % 6
-            if i == 0:
-                return v, t, p
-            if i == 1:
-                return q, v, p
-            if i == 2:
-                return p, v, t
-            if i == 3:
-                return p, q, v
-            if i == 4:
-                return t, p, v
-            if i == 5:
-                return v, p, q
-
-        golden_ratio = 0.618033988749895
-        h = 0.22717784590367374
-
-        for _unused in xrange(n):
-            h += golden_ratio
-            h %= 1
-            HSV_tuple = [h, 0.95, 0.95]
-            RGB_tuple = hsv_to_rgb(*HSV_tuple)
-            yield map(lambda x: str(int(x * 256)), RGB_tuple)
-
-    cgenerator = gen_color()
-
     def get_color_string(cs):
         if cs in color_dict:
             col = color_dict[cs]
         else:
-            col = color_dict[cs] = cgenerator.next()
+            col = color_dict[cs] = next(cgenerator)
         return "color: rgb(%s)! important;" % (', '.join(col))
 
-    def url_func(repo_name):
-
-        def _url_func(changeset):
-            author = escape(changeset.author)
-            date = changeset.date
-            message = escape(changeset.message)
-            tooltip_html = ("<b>Author:</b> %s<br/>"
-                            "<b>Date:</b> %s</b><br/>"
-                            "<b>Message:</b> %s") % (author, date, message)
+    def url_func(changeset):
+        author = escape(changeset.author)
+        date = changeset.date
+        message = escape(changeset.message)
+        tooltip_html = ("<b>Author:</b> %s<br/>"
+                        "<b>Date:</b> %s</b><br/>"
+                        "<b>Message:</b> %s") % (author, date, message)
 
-            lnk_format = show_id(changeset)
-            uri = link_to(
-                    lnk_format,
-                    url('changeset_home', repo_name=repo_name,
-                        revision=changeset.raw_id),
-                    style=get_color_string(changeset.raw_id),
-                    **{'data-toggle': 'popover',
-                       'data-content': tooltip_html}
-                  )
+        lnk_format = show_id(changeset)
+        uri = link_to(
+                lnk_format,
+                url('changeset_home', repo_name=repo_name,
+                    revision=changeset.raw_id),
+                style=get_color_string(changeset.raw_id),
+                **{'data-toggle': 'popover',
+                   'data-content': tooltip_html}
+              )
 
-            uri += '\n'
-            return uri
-        return _url_func
+        uri += '\n'
+        return uri
 
-    return literal(markup_whitespace(annotate_highlight(filenode, url_func(repo_name), **kwargs)))
+    return literal(markup_whitespace(annotate_highlight(filenode, url_func, **kwargs)))
 
 
 class _Message(object):
@@ -424,22 +418,14 @@
     Converting the message to a string returns the message text. Instances
     also have the following attributes:
 
-    * ``message``: the message text.
     * ``category``: the category specified when the message was created.
+    * ``message``: the html-safe message text.
     """
 
     def __init__(self, category, message):
         self.category = category
         self.message = message
 
-    def __str__(self):
-        return self.message
-
-    __unicode__ = __str__
-
-    def __html__(self):
-        return escape(safe_unicode(self.message))
-
 
 def _session_flash_messages(append=None, clear=False):
     """Manage a message queue in tg.session: return the current message queue
@@ -461,7 +447,7 @@
     return flash_messages
 
 
-def flash(message, category=None, logf=None):
+def flash(message, category, logf=None):
     """
     Show a message to the user _and_ log it through the specified function
 
@@ -471,14 +457,22 @@
     logf defaults to log.info, unless category equals 'success', in which
     case logf defaults to log.debug.
     """
+    assert category in ('error', 'success', 'warning'), category
+    if hasattr(message, '__html__'):
+        # render to HTML for storing in cookie
+        safe_message = str(message)
+    else:
+        # Apply str - the message might be an exception with __str__
+        # Escape, so we can trust the result without further escaping, without any risk of injection
+        safe_message = html_escape(str(message))
     if logf is None:
         logf = log.info
         if category == 'success':
             logf = log.debug
 
-    logf('Flash %s: %s', category, message)
+    logf('Flash %s: %s', category, safe_message)
 
-    _session_flash_messages(append=(category, message))
+    _session_flash_messages(append=(category, safe_message))
 
 
 def pop_flash_messages():
@@ -486,7 +480,7 @@
 
     The return value is a list of ``Message`` objects.
     """
-    return [_Message(*m) for m in _session_flash_messages(clear=True)]
+    return [_Message(category, message) for category, message in _session_flash_messages(clear=True)]
 
 
 age = lambda x, y=False: _age(x, y)
@@ -516,8 +510,7 @@
 
 def fmt_date(date):
     if date:
-        return date.strftime("%Y-%m-%d %H:%M:%S").decode('utf-8')
-
+        return date.strftime("%Y-%m-%d %H:%M:%S")
     return ""
 
 
@@ -677,7 +670,7 @@
             return _op, _name
 
         revs = []
-        if len(filter(lambda v: v != '', revs_ids)) > 0:
+        if len([v for v in revs_ids if v != '']) > 0:
             repo = None
             for rev in revs_ids[:revs_top_limit]:
                 _op, _name = _get_op(rev)
@@ -937,13 +930,13 @@
     if email_address == _def:
         return default
 
-    parsed_url = urlparse.urlparse(url.current(qualified=True))
+    parsed_url = urllib.parse.urlparse(url.current(qualified=True))
     url = (c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL) \
                .replace('{email}', email_address) \
-               .replace('{md5email}', hashlib.md5(safe_str(email_address).lower()).hexdigest()) \
+               .replace('{md5email}', hashlib.md5(safe_bytes(email_address).lower()).hexdigest()) \
                .replace('{netloc}', parsed_url.netloc) \
                .replace('{scheme}', parsed_url.scheme) \
-               .replace('{size}', safe_str(size))
+               .replace('{size}', str(size))
     return url
 
 
@@ -959,7 +952,7 @@
         suf = ''
         if len(nodes) > 30:
             suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
-        return literal(pref + '<br/> '.join([safe_unicode(x.path)
+        return literal(pref + '<br/> '.join([x.path
                                              for x in nodes[:30]]) + suf)
     else:
         return ': ' + _('No files')
@@ -1165,7 +1158,7 @@
         tmp_urlify_issues_f = lambda s: s
 
         issue_pat_re = re.compile(r'issue_pat(.*)')
-        for k in CONFIG.keys():
+        for k in CONFIG:
             # Find all issue_pat* settings that also have corresponding server_link and prefix configuration
             m = issue_pat_re.match(k)
             if m is None:
@@ -1229,7 +1222,7 @@
     Render plain text with revision hashes and issue references urlified
     and with @mention highlighting.
     """
-    s = safe_unicode(source)
+    s = safe_str(source)
     s = urlify_text(s, repo_name=repo_name)
     return literal('<div class="formatted-fixed">%s</div>' % s)
 
--- a/kallithea/lib/hooks.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/hooks.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,16 +25,17 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-import binascii
 import os
+import sys
 import time
 
+import mercurial.scmutil
+
 from kallithea.lib import helpers as h
 from kallithea.lib.exceptions import UserCreationError
-from kallithea.lib.utils import action_logger, make_ui, setup_cache_regions
-from kallithea.lib.utils2 import get_hook_environment, safe_str, safe_unicode
+from kallithea.lib.utils import action_logger, make_ui
+from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str
 from kallithea.lib.vcs.backends.base import EmptyChangeset
-from kallithea.lib.vcs.utils.hgcompat import revrange
 from kallithea.model.db import Repository, User
 
 
@@ -43,7 +44,7 @@
         alias += '.'
 
     size_scm, size_root = 0, 0
-    for path, dirs, files in os.walk(safe_str(root_path)):
+    for path, dirs, files in os.walk(root_path):
         if path.find(alias) != -1:
             for f in files:
                 try:
@@ -65,16 +66,16 @@
 
 
 def repo_size(ui, repo, hooktype=None, **kwargs):
-    """Presents size of repository after push"""
-    size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
+    """Show size of Mercurial repository, to be called after push."""
+    size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', safe_str(repo.root))
 
     last_cs = repo[len(repo) - 1]
 
     msg = ('Repository size .hg: %s Checkout: %s Total: %s\n'
            'Last revision is now r%s:%s\n') % (
-        size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
+        size_hg_f, size_root_f, size_total_f, last_cs.rev(), ascii_str(last_cs.hex())[:12]
     )
-    ui.status(msg)
+    ui.status(safe_bytes(msg))
 
 
 def log_pull_action(ui, repo, **kwargs):
@@ -109,8 +110,7 @@
     Note: This hook is not only logging, but also the side effect invalidating
     cahes! The function should perhaps be renamed.
     """
-    _h = binascii.hexlify
-    revs = [_h(repo[r].node()) for r in revrange(repo, [node + ':' + node_last])]
+    revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])]
     process_pushed_raw_ids(revs)
     return 0
 
@@ -302,31 +302,23 @@
     they thus need enough info to be able to create an app environment and
     connect to the database.
     """
-    from paste.deploy import appconfig
-    from sqlalchemy import engine_from_config
-    from kallithea.config.environment import load_environment
-    from kallithea.model.base import init_model
+    import paste.deploy
+    import kallithea.config.middleware
 
     extras = get_hook_environment()
-    ini_file_path = extras['config']
-    #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging
-    app_conf = appconfig('config:%s' % ini_file_path)
-    conf = load_environment(app_conf.global_conf, app_conf.local_conf)
 
-    setup_cache_regions(conf)
+    path_to_ini_file = extras['config']
+    kallithea.CONFIG = paste.deploy.appconfig('config:' + path_to_ini_file)
+    #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging
+    kallithea.config.middleware.make_app(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
 
-    engine = engine_from_config(conf, 'sqlalchemy.')
-    init_model(engine)
-
-    repo_path = safe_unicode(repo_path)
     # fix if it's not a bare repo
     if repo_path.endswith(os.sep + '.git'):
         repo_path = repo_path[:-5]
 
     repo = Repository.get_by_full_path(repo_path)
     if not repo:
-        raise OSError('Repository %s not found in database'
-                      % (safe_str(repo_path)))
+        raise OSError('Repository %s not found in database' % repo_path)
 
     baseui = make_ui()
     return baseui, repo
@@ -340,7 +332,11 @@
 
 def handle_git_post_receive(repo_path, git_stdin_lines):
     """Called from Git post-receive hook"""
-    baseui, repo = _hook_environment(repo_path)
+    try:
+        baseui, repo = _hook_environment(repo_path)
+    except HookEnvironmentError as e:
+        sys.stderr.write("Skipping Kallithea Git post-recieve hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e))
+        return 0
 
     # the post push hook should never use the cached instance
     scm_repo = repo.scm_instance_no_cache()
@@ -363,19 +359,20 @@
             if push_ref['old_rev'] == EmptyChangeset().raw_id:
                 # update the symbolic ref if we push new repo
                 if scm_repo.is_empty():
-                    scm_repo._repo.refs.set_symbolic_ref('HEAD',
-                                        'refs/heads/%s' % push_ref['name'])
+                    scm_repo._repo.refs.set_symbolic_ref(
+                        b'HEAD',
+                        b'refs/heads/%s' % safe_bytes(push_ref['name']))
 
                 # build exclude list without the ref
                 cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*']
-                stdout, stderr = scm_repo.run_git_command(cmd)
+                stdout = scm_repo.run_git_command(cmd)
                 ref = push_ref['ref']
                 heads = [head for head in stdout.splitlines() if head != ref]
                 # now list the git revs while excluding from the list
                 cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H']
                 cmd.append('--not')
                 cmd.extend(heads) # empty list is ok
-                stdout, stderr = scm_repo.run_git_command(cmd)
+                stdout = scm_repo.run_git_command(cmd)
                 git_revs += stdout.splitlines()
 
             elif push_ref['new_rev'] == EmptyChangeset().raw_id:
@@ -384,7 +381,7 @@
             else:
                 cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref,
                        '--reverse', '--pretty=format:%H']
-                stdout, stderr = scm_repo.run_git_command(cmd)
+                stdout = scm_repo.run_git_command(cmd)
                 git_revs += stdout.splitlines()
 
         elif _type == 'tags':
@@ -399,5 +396,5 @@
 def rejectpush(ui, **kwargs):
     """Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos"""
     ex = get_hook_environment()
-    ui.warn((b"Push access to %r denied\n") % safe_str(ex.repository))
+    ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository))
     return 1
--- a/kallithea/lib/indexers/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/indexers/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -146,7 +146,7 @@
             docnum = self.matcher.id()
             chunks = [offsets for offsets in self.get_chunks()]
             docs_id.append([docnum, chunks])
-            self.matcher.next()
+            self.matcher.next()  # this looks like a py2 iterator ... but it isn't
         return docs_id
 
     def __str__(self):
@@ -203,8 +203,7 @@
         return res
 
     def get_short_content(self, res, chunks):
-
-        return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
+        return u''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
 
     def get_chunks(self):
         """
@@ -213,7 +212,11 @@
         close occurrences twice.
         """
         memory = [(0, 0)]
-        if self.matcher.supports('positions'):
+        try:
+            supports_positions = self.matcher.supports('positions')
+        except AttributeError:  # 'NoneType' object has no attribute 'supports' (because matcher never get a format)
+            supports_positions = False
+        if supports_positions:
             for span in self.matcher.spans():
                 start = span.startchar or 0
                 end = span.endchar or 0
--- a/kallithea/lib/indexers/daemon.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/indexers/daemon.py	Thu Feb 06 01:19:23 2020 +0100
@@ -39,7 +39,7 @@
 
 from kallithea.config.conf import INDEX_EXTENSIONS, INDEX_FILENAMES
 from kallithea.lib.indexers import CHGSET_IDX_NAME, CHGSETS_SCHEMA, IDX_NAME, SCHEMA
-from kallithea.lib.utils2 import safe_str, safe_unicode
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs.exceptions import ChangesetError, NodeDoesNotExistError, RepositoryError
 from kallithea.model.db import Repository
 from kallithea.model.scm import ScmModel
@@ -77,8 +77,7 @@
 
         # filter repo list
         if repo_list:
-            # Fix non-ascii repo names to unicode
-            repo_list = map(safe_unicode, repo_list)
+            repo_list = set(repo_list)
             self.filtered_repo_paths = {}
             for repo_name, repo in self.repo_paths.items():
                 if repo_name in repo_list:
@@ -110,7 +109,7 @@
             self.initial = False
 
     def _get_index_revision(self, repo):
-        db_repo = Repository.get_by_repo_name(repo.name_unicode)
+        db_repo = Repository.get_by_repo_name(repo.name)
         landing_rev = 'tip'
         if db_repo:
             _rev_type, _rev = db_repo.landing_rev
@@ -133,7 +132,7 @@
             cs = self._get_index_changeset(repo)
             for _topnode, _dirs, files in cs.walk('/'):
                 for f in files:
-                    index_paths_.add(os.path.join(safe_str(repo.path), safe_str(f.path)))
+                    index_paths_.add(os.path.join(repo.path, f.path))
 
         except RepositoryError:
             log.debug(traceback.format_exc())
@@ -142,19 +141,16 @@
 
     def get_node(self, repo, path, index_rev=None):
         """
-        gets a filenode based on given full path. It operates on string for
-        hg git compatibility.
+        gets a filenode based on given full path.
 
         :param repo: scm repo instance
         :param path: full path including root location
         :return: FileNode
         """
         # FIXME: paths should be normalized ... or even better: don't include repo.path
-        path = safe_str(path)
-        repo_path = safe_str(repo.path)
-        assert path.startswith(repo_path)
-        assert path[len(repo_path)] in (os.path.sep, os.path.altsep)
-        node_path = path[len(repo_path) + 1:]
+        assert path.startswith(repo.path)
+        assert path[len(repo.path)] in (os.path.sep, os.path.altsep)
+        node_path = path[len(repo.path) + 1:]
         cs = self._get_index_changeset(repo, index_rev=index_rev)
         node = cs.get_node(node_path)
         return node
@@ -182,12 +178,13 @@
 
         indexed = indexed_w_content = 0
         if self.is_indexable_node(node):
-            u_content = node.content
-            if not isinstance(u_content, unicode):
+            bytes_content = node.content
+            if b'\0' in bytes_content:
                 log.warning('    >> %s - no text content', path)
                 u_content = u''
             else:
                 log.debug('    >> %s', path)
+                u_content = safe_str(bytes_content)
                 indexed_w_content += 1
 
         else:
@@ -196,13 +193,12 @@
             u_content = u''
             indexed += 1
 
-        p = safe_unicode(path)
         writer.add_document(
-            fileid=p,
-            owner=unicode(repo.contact),
-            repository_rawname=safe_unicode(repo_name),
-            repository=safe_unicode(repo_name),
-            path=p,
+            fileid=path,
+            owner=repo.contact,
+            repository_rawname=repo_name,
+            repository=repo_name,
+            path=path,
             content=u_content,
             modtime=self.get_node_mtime(node),
             extension=node.extension
@@ -237,18 +233,18 @@
             indexed += 1
             log.debug('    >> %s %s/%s', cs, indexed, total)
             writer.add_document(
-                raw_id=unicode(cs.raw_id),
-                owner=unicode(repo.contact),
+                raw_id=cs.raw_id,
+                owner=repo.contact,
                 date=cs._timestamp,
-                repository_rawname=safe_unicode(repo_name),
-                repository=safe_unicode(repo_name),
+                repository_rawname=repo_name,
+                repository=repo_name,
                 author=cs.author,
                 message=cs.message,
                 last=cs.last,
-                added=u' '.join([safe_unicode(node.path) for node in cs.added]).lower(),
-                removed=u' '.join([safe_unicode(node.path) for node in cs.removed]).lower(),
-                changed=u' '.join([safe_unicode(node.path) for node in cs.changed]).lower(),
-                parents=u' '.join([cs.raw_id for cs in cs.parents]),
+                added=u' '.join(node.path for node in cs.added).lower(),
+                removed=u' '.join(node.path for node in cs.removed).lower(),
+                changed=u' '.join(node.path for node in cs.changed).lower(),
+                parents=u' '.join(cs.raw_id for cs in cs.parents),
             )
 
         return indexed
@@ -330,8 +326,8 @@
                     log.debug('>> NOTHING TO COMMIT TO CHANGESET INDEX<<')
 
     def update_file_index(self):
-        log.debug((u'STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s '
-                   'AND REPOS %s') % (INDEX_EXTENSIONS, self.repo_paths.keys()))
+        log.debug(u'STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s '
+                  'AND REPOS %s', INDEX_EXTENSIONS, ' and '.join(self.repo_paths))
 
         idx = open_dir(self.index_location, indexname=self.indexname)
         # The set of all paths in the index
@@ -390,9 +386,7 @@
                 ri_cnt = 0   # indexed
                 riwc_cnt = 0  # indexed with content
                 for path in self.get_paths(repo):
-                    path = safe_unicode(path)
                     if path in to_index or path not in indexed_paths:
-
                         # This is either a file that's changed, or a new file
                         # that wasn't indexed before. So index it!
                         i, iwc = self.add_doc(writer, path, repo, repo_name)
@@ -431,7 +425,7 @@
         file_idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
         file_idx_writer = file_idx.writer()
         log.debug('BUILDING INDEX FOR EXTENSIONS %s '
-                  'AND REPOS %s' % (INDEX_EXTENSIONS, self.repo_paths.keys()))
+                  'AND REPOS %s', INDEX_EXTENSIONS, ' and '.join(self.repo_paths))
 
         for repo_name, repo in sorted(self.repo_paths.items()):
             log.debug('Updating indices for repo %s', repo_name)
--- a/kallithea/lib/inifile.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/inifile.py	Thu Feb 06 01:19:23 2020 +0100
@@ -72,7 +72,7 @@
     ...     '[third-section]': {'third_extra': ' 3'},
     ...     '[fourth-section]': {'fourth_extra': '4', 'fourth': '"four"'},
     ... }
-    >>> print expand(template, mako_variable_values, settings)
+    >>> print(expand(template, mako_variable_values, settings))
     <BLANKLINE>
     [first-section]
     <BLANKLINE>
--- a/kallithea/lib/markup_renderer.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/markup_renderer.py	Thu Feb 06 01:19:23 2020 +0100
@@ -33,7 +33,7 @@
 import bleach
 import markdown as markdown_mod
 
-from kallithea.lib.utils2 import MENTIONS_REGEX, safe_unicode
+from kallithea.lib.utils2 import MENTIONS_REGEX, safe_str
 
 
 log = logging.getLogger(__name__)
@@ -119,17 +119,17 @@
         At last it will just do a simple html replacing new lines with <br/>
 
         >>> MarkupRenderer.render('''<img id="a" style="margin-top:-1000px;color:red" src="http://example.com/test.jpg">''', '.md')
-        u'<p><img id="a" src="http://example.com/test.jpg" style="color: red;"></p>'
+        '<p><img id="a" src="http://example.com/test.jpg" style="color: red;"></p>'
         >>> MarkupRenderer.render('''<img class="c d" src="file://localhost/test.jpg">''', 'b.mkd')
-        u'<p><img class="c d"></p>'
+        '<p><img class="c d"></p>'
         >>> MarkupRenderer.render('''<a href="foo">foo</a>''', 'c.mkdn')
-        u'<p><a href="foo">foo</a></p>'
+        '<p><a href="foo">foo</a></p>'
         >>> MarkupRenderer.render('''<script>alert(1)</script>''', 'd.mdown')
-        u'&lt;script&gt;alert(1)&lt;/script&gt;'
+        '&lt;script&gt;alert(1)&lt;/script&gt;'
         >>> MarkupRenderer.render('''<div onclick="alert(2)">yo</div>''', 'markdown')
-        u'<div>yo</div>'
+        '<div>yo</div>'
         >>> MarkupRenderer.render('''<a href="javascript:alert(3)">yo</a>''', 'md')
-        u'<p><a>yo</a></p>'
+        '<p><a>yo</a></p>'
         """
 
         renderer = cls._detect_renderer(source, filename)
@@ -150,7 +150,7 @@
 
     @classmethod
     def plain(cls, source, universal_newline=True):
-        source = safe_unicode(source)
+        source = safe_str(source)
         if universal_newline:
             newline = '\n'
             source = newline.join(source.splitlines())
@@ -168,30 +168,30 @@
         with "safe" fall-back to plaintext. Output from this method should be sanitized before use.
 
         >>> MarkupRenderer.markdown('''<img id="a" style="margin-top:-1000px;color:red" src="http://example.com/test.jpg">''')
-        u'<p><img id="a" style="margin-top:-1000px;color:red" src="http://example.com/test.jpg"></p>'
+        '<p><img id="a" style="margin-top:-1000px;color:red" src="http://example.com/test.jpg"></p>'
         >>> MarkupRenderer.markdown('''<img class="c d" src="file://localhost/test.jpg">''')
-        u'<p><img class="c d" src="file://localhost/test.jpg"></p>'
+        '<p><img class="c d" src="file://localhost/test.jpg"></p>'
         >>> MarkupRenderer.markdown('''<a href="foo">foo</a>''')
-        u'<p><a href="foo">foo</a></p>'
+        '<p><a href="foo">foo</a></p>'
         >>> MarkupRenderer.markdown('''<script>alert(1)</script>''')
-        u'<script>alert(1)</script>'
+        '<script>alert(1)</script>'
         >>> MarkupRenderer.markdown('''<div onclick="alert(2)">yo</div>''')
-        u'<div onclick="alert(2)">yo</div>'
+        '<div onclick="alert(2)">yo</div>'
         >>> MarkupRenderer.markdown('''<a href="javascript:alert(3)">yo</a>''')
-        u'<p><a href="javascript:alert(3)">yo</a></p>'
+        '<p><a href="javascript:alert(3)">yo</a></p>'
         >>> MarkupRenderer.markdown('''## Foo''')
-        u'<h2>Foo</h2>'
-        >>> print MarkupRenderer.markdown('''
+        '<h2>Foo</h2>'
+        >>> print(MarkupRenderer.markdown('''
         ...     #!/bin/bash
         ...     echo "hello"
-        ... ''')
+        ... '''))
         <table class="code-highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
         2</pre></div></td><td class="code"><div class="code-highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
         <span class="nb">echo</span> <span class="s2">&quot;hello&quot;</span>
         </pre></div>
         </td></tr></table>
         """
-        source = safe_unicode(source)
+        source = safe_str(source)
         try:
             if flavored:
                 source = cls._flavored_markdown(source)
@@ -209,7 +209,7 @@
 
     @classmethod
     def rst(cls, source, safe=True):
-        source = safe_unicode(source)
+        source = safe_str(source)
         try:
             from docutils.core import publish_parts
             from docutils.parsers.rst import directives
@@ -219,7 +219,7 @@
             docutils_settings.update({'input_encoding': 'unicode',
                                       'report_level': 4})
 
-            for k, v in docutils_settings.iteritems():
+            for k, v in docutils_settings.items():
                 directives.register_directive(k, v)
 
             parts = publish_parts(source=source,
--- a/kallithea/lib/middleware/permanent_repo_url.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/middleware/permanent_repo_url.py	Thu Feb 06 01:19:23 2020 +0100
@@ -20,7 +20,8 @@
 """
 
 
-from kallithea.lib.utils import fix_repo_id_name, safe_str
+from kallithea.lib.utils import fix_repo_id_name
+from kallithea.lib.utils2 import safe_bytes, safe_str
 
 
 class PermanentRepoUrl(object):
@@ -30,9 +31,11 @@
         self.config = config
 
     def __call__(self, environ, start_response):
-        path_info = environ['PATH_INFO']
+        # Extract path_info as get_path_info does, but do it explicitly because
+        # we also have to do the reverse operation when patching it back in
+        path_info = safe_str(environ['PATH_INFO'].encode('latin1'))
         if path_info.startswith('/'): # it must
-            path_info = '/' + safe_str(fix_repo_id_name(path_info[1:]))
-            environ['PATH_INFO'] = path_info
+            path_info = '/' + fix_repo_id_name(path_info[1:])
+            environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1')
 
         return self.application(environ, start_response)
--- a/kallithea/lib/middleware/pygrack.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/middleware/pygrack.py	Thu Feb 06 01:19:23 2020 +0100
@@ -33,7 +33,7 @@
 from webob import Request, Response, exc
 
 import kallithea
-from kallithea.lib.utils2 import safe_unicode
+from kallithea.lib.utils2 import ascii_bytes
 from kallithea.lib.vcs import subprocessio
 
 
@@ -87,7 +87,6 @@
 
         :param path:
         """
-        path = safe_unicode(path)
         assert path.startswith('/' + self.repo_name + '/')
         return path[len(self.repo_name) + 2:].strip('/')
 
@@ -113,14 +112,14 @@
         #                     ref_list
         #                     "0000"
         server_advert = '# service=%s\n' % git_command
-        packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
+        packet_len = 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(cmd,
-                starting_values=[packet_len + server_advert + '0000']
+                starting_values=[ascii_bytes(packet_len + server_advert + '0000')]
             )
         except EnvironmentError as e:
             log.error(traceback.format_exc())
@@ -186,7 +185,7 @@
         _path = self._get_fixedpath(req.path_info)
         if _path.startswith('info/refs'):
             app = self.inforefs
-        elif [a for a in self.valid_accepts if a in req.accept]:
+        elif req.accept.acceptable_offers(self.valid_accepts):
             app = self.backend
         try:
             resp = app(req, environ)
--- a/kallithea/lib/middleware/simplegit.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/middleware/simplegit.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,11 +31,10 @@
 import logging
 import re
 
-from kallithea.lib.base import BaseVCSController
+from kallithea.lib.base import BaseVCSController, get_path_info
 from kallithea.lib.hooks import log_pull_action
 from kallithea.lib.middleware.pygrack import make_wsgi_app
 from kallithea.lib.utils import make_ui
-from kallithea.lib.utils2 import safe_unicode
 from kallithea.model.db import Repository
 
 
@@ -57,14 +56,14 @@
 
     @classmethod
     def parse_request(cls, environ):
-        path_info = environ.get('PATH_INFO', '')
+        path_info = get_path_info(environ)
         m = GIT_PROTO_PAT.match(path_info)
         if m is None:
             return None
 
         class parsed_request(object):
             # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol
-            repo_name = safe_unicode(m.group(1).rstrip('/'))
+            repo_name = m.group(1).rstrip('/')
             cmd = m.group(2)
 
             query_string = environ['QUERY_STRING']
--- a/kallithea/lib/middleware/simplehg.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/middleware/simplehg.py	Thu Feb 06 01:19:23 2020 +0100
@@ -30,12 +30,13 @@
 
 import logging
 import os
-import urllib
+import urllib.parse
+
+import mercurial.hgweb
 
-from kallithea.lib.base import BaseVCSController
+from kallithea.lib.base import BaseVCSController, get_path_info
 from kallithea.lib.utils import make_ui
-from kallithea.lib.utils2 import safe_str, safe_unicode
-from kallithea.lib.vcs.utils.hgcompat import hgweb_mod
+from kallithea.lib.utils2 import safe_bytes
 
 
 log = logging.getLogger(__name__)
@@ -99,12 +100,12 @@
         http_accept = environ.get('HTTP_ACCEPT', '')
         if not http_accept.startswith('application/mercurial'):
             return None
-        path_info = environ.get('PATH_INFO', '')
+        path_info = get_path_info(environ)
         if not path_info.startswith('/'): # it must!
             return None
 
         class parsed_request(object):
-            repo_name = safe_unicode(path_info[1:].rstrip('/'))
+            repo_name = path_info[1:].rstrip('/')
 
             query_string = environ['QUERY_STRING']
 
@@ -120,7 +121,7 @@
                             break
                         action = 'pull'
                         for cmd_arg in hgarg[5:].split(';'):
-                            cmd, _args = urllib.unquote_plus(cmd_arg).split(' ', 1)
+                            cmd, _args = urllib.parse.unquote_plus(cmd_arg).split(' ', 1)
                             op = cmd_mapping.get(cmd, 'push')
                             if op != 'pull':
                                 assert op == 'push'
@@ -136,13 +137,13 @@
         """
         Make an hgweb wsgi application.
         """
-        str_repo_name = safe_str(parsed_request.repo_name)
-        repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
+        repo_name = parsed_request.repo_name
+        repo_path = os.path.join(self.basepath, repo_name)
         baseui = make_ui(repo_path=repo_path)
-        hgweb_app = hgweb_mod.hgweb(repo_path, name=str_repo_name, baseui=baseui)
+        hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
 
         def wrapper_app(environ, start_response):
-            environ['REPO_NAME'] = str_repo_name # used by hgweb_mod.hgweb
+            environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb
             return hgweb_app(environ, start_response)
 
         return wrapper_app
--- a/kallithea/lib/middleware/wrapper.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/middleware/wrapper.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,8 +29,7 @@
 import logging
 import time
 
-from kallithea.lib.base import _get_access_path, _get_ip_addr
-from kallithea.lib.utils2 import safe_unicode
+from kallithea.lib.base import _get_ip_addr, get_path_info
 
 
 log = logging.getLogger(__name__)
@@ -64,14 +63,14 @@
 
     def __init__(self, result, meter, description):
         self._result_close = getattr(result, 'close', None) or (lambda: None)
-        self._next = iter(result).next
+        self._next = iter(result).__next__
         self._meter = meter
         self._description = description
 
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         chunk = self._next()
         self._meter.measure(chunk)
         return chunk
@@ -91,7 +90,7 @@
         meter = Meter(start_response)
         description = "Request from %s for %s" % (
             _get_ip_addr(environ),
-            safe_unicode(_get_access_path(environ)),
+            get_path_info(environ),
         )
         try:
             result = self.application(environ, meter.start_response)
--- a/kallithea/lib/page.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/page.py	Thu Feb 06 01:19:23 2020 +0100
@@ -15,11 +15,11 @@
 Custom paging classes
 """
 import logging
-import math
-import re
 
-from webhelpers2.html import HTML, literal
-from webhelpers.paginate import Page as _Page
+import paginate
+import paginate_sqlalchemy
+import sqlalchemy.orm
+from webhelpers2.html import literal
 
 from kallithea.config.routing import url
 
@@ -27,229 +27,36 @@
 log = logging.getLogger(__name__)
 
 
-class Page(_Page):
-    """
-    Custom pager emitting Bootstrap paginators
-    """
-
-    def __init__(self, *args, **kwargs):
-        kwargs.setdefault('url', url.current)
-        _Page.__init__(self, *args, **kwargs)
-
-    def _get_pos(self, cur_page, max_page, items):
-        edge = (items / 2) + 1
-        if (cur_page <= edge):
-            radius = max(items / 2, items - cur_page)
-        elif (max_page - cur_page) < edge:
-            radius = (items - 1) - (max_page - cur_page)
-        else:
-            radius = items / 2
-
-        left = max(1, (cur_page - (radius)))
-        right = min(max_page, cur_page + (radius))
-        return left, cur_page, right
-
-    def _range(self, regexp_match):
-        """
-        Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
-
-        Arguments:
-
-        regexp_match
-            A "re" (regular expressions) match object containing the
-            radius of linked pages around the current page in
-            regexp_match.group(1) as a string
-
-        This function is supposed to be called as a callable in
-        re.sub.
-
-        """
-        radius = int(regexp_match.group(1))
-
-        # Compute the first and last page number within the radius
-        # e.g. '1 .. 5 6 [7] 8 9 .. 12'
-        # -> leftmost_page  = 5
-        # -> rightmost_page = 9
-        leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
-                                                            self.last_page,
-                                                            (radius * 2) + 1)
-        nav_items = []
-
-        # Create a link to the first page (unless we are on the first page
-        # or there would be no need to insert '..' spacers)
-        if self.page != self.first_page and self.first_page < leftmost_page:
-            nav_items.append(HTML.li(self._pagerlink(self.first_page, self.first_page)))
+class Page(paginate.Page):
 
-        # Insert dots if there are pages between the first page
-        # and the currently displayed page range
-        if leftmost_page - self.first_page > 1:
-            # Wrap in a SPAN tag if nolink_attr is set
-            text_ = '..'
-            if self.dotdot_attr:
-                text_ = HTML.span(c=text_, **self.dotdot_attr)
-            nav_items.append(HTML.li(text_))
-
-        for thispage in xrange(leftmost_page, rightmost_page + 1):
-            # Highlight the current page number and do not use a link
-            text_ = str(thispage)
-            if thispage == self.page:
-                # Wrap in a SPAN tag if nolink_attr is set
-                if self.curpage_attr:
-                    text_ = HTML.li(HTML.span(c=text_), **self.curpage_attr)
-                nav_items.append(text_)
-            # Otherwise create just a link to that page
-            else:
-                nav_items.append(HTML.li(self._pagerlink(thispage, text_)))
-
-        # Insert dots if there are pages between the displayed
-        # page numbers and the end of the page range
-        if self.last_page - rightmost_page > 1:
-            text_ = '..'
-            # Wrap in a SPAN tag if nolink_attr is set
-            if self.dotdot_attr:
-                text_ = HTML.span(c=text_, **self.dotdot_attr)
-            nav_items.append(HTML.li(text_))
-
-        # Create a link to the very last page (unless we are on the last
-        # page or there would be no need to insert '..' spacers)
-        if self.page != self.last_page and rightmost_page < self.last_page:
-            nav_items.append(HTML.li(self._pagerlink(self.last_page, self.last_page)))
-
-        #_page_link = url.current()
-        #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
-        #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
-        return self.separator.join(nav_items)
-
-    def pager(self, format='<ul class="pagination">$link_previous ~2~ $link_next</ul>', page_param='page', partial_param='partial',
-        show_if_single_page=False, separator=' ', onclick=None,
-        symbol_first='<<', symbol_last='>>',
-        symbol_previous='<', symbol_next='>',
-        link_attr=None,
-        curpage_attr=None,
-        dotdot_attr=None, **kwargs
-    ):
-        self.curpage_attr = curpage_attr or {'class': 'active'}
-        self.separator = separator
-        self.pager_kwargs = kwargs
-        self.page_param = page_param
-        self.partial_param = partial_param
-        self.onclick = onclick
-        self.link_attr = link_attr or {'class': 'pager_link', 'rel': 'prerender'}
-        self.dotdot_attr = dotdot_attr or {'class': 'pager_dotdot'}
+    def __init__(self, collection,
+                 page=1, items_per_page=20, item_count=None,
+                 **kwargs):
+        if isinstance(collection, sqlalchemy.orm.query.Query):
+            collection = paginate_sqlalchemy.SqlalchemyOrmWrapper(collection)
+        paginate.Page.__init__(self, collection, page=page, items_per_page=items_per_page, item_count=item_count,
+                               url_maker=lambda page: url.current(page=page, **kwargs))
 
-        # Don't show navigator if there is no more than one page
-        if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
-            return ''
-
-        from string import Template
-        # Replace ~...~ in token format by range of pages
-        result = re.sub(r'~(\d+)~', self._range, format)
-
-        # Interpolate '%' variables
-        result = Template(result).safe_substitute({
-            'first_page': self.first_page,
-            'last_page': self.last_page,
-            'page': self.page,
-            'page_count': self.page_count,
-            'items_per_page': self.items_per_page,
-            'first_item': self.first_item,
-            'last_item': self.last_item,
-            'item_count': self.item_count,
-            'link_first': self.page > self.first_page and
-                    self._pagerlink(self.first_page, symbol_first) or '',
-            'link_last': self.page < self.last_page and
-                    self._pagerlink(self.last_page, symbol_last) or '',
-            'link_previous': HTML.li(self.previous_page and
-                    self._pagerlink(self.previous_page, symbol_previous)
-                    or HTML.a(symbol_previous)),
-            'link_next': HTML.li(self.next_page and
-                    self._pagerlink(self.next_page, symbol_next)
-                    or HTML.a(symbol_next)),
-        })
-
-        return literal(result)
-
-
-class RepoPage(Page):
-
-    def __init__(self, collection, page=1, items_per_page=20,
-                 item_count=None, **kwargs):
-
-        """Create a "RepoPage" instance. special pager for paging
-        repository
-        """
-        # TODO: call baseclass __init__
-        self._url_generator = kwargs.pop('url', url.current)
-
-        # Safe the kwargs class-wide so they can be used in the pager() method
-        self.kwargs = kwargs
-
-        # Save a reference to the collection
-        self.original_collection = collection
-
-        self.collection = collection
+    def pager(self):
+        return literal(
+            paginate.Page.pager(self,
+                format='<ul class="pagination">$link_previous\n~4~$link_next</ul>',
+                link_attr={'class': 'pager_link'},
+                dotdot_attr={'class': 'pager_dotdot'},
+                separator='\n',
+                ))
 
-        # The self.page is the number of the current page.
-        # The first page has the number 1!
-        try:
-            self.page = int(page)  # make it int() if we get it as a string
-        except (ValueError, TypeError):
-            log.error("Invalid page value: %r", page)
-            self.page = 1
-
-        self.items_per_page = items_per_page
-
-        # Unless the user tells us how many items the collections has
-        # we calculate that ourselves.
-        if item_count is not None:
-            self.item_count = item_count
-        else:
-            self.item_count = len(self.collection)
-
-        # Compute the number of the first and last available page
-        if self.item_count > 0:
-            self.first_page = 1
-            self.page_count = int(math.ceil(float(self.item_count) /
-                                            self.items_per_page))
-            self.last_page = self.first_page + self.page_count - 1
-
-            # Make sure that the requested page number is the range of
-            # valid pages
-            if self.page > self.last_page:
-                self.page = self.last_page
-            elif self.page < self.first_page:
-                self.page = self.first_page
+    @staticmethod
+    def default_link_tag(item):
+        # based on the base class implementation, but wrapping results in <li>, and with different handling of current_page
+        text = item['value']
+        if item['type'] == 'current_page':  # we need active on the li and can thus not use curpage_attr
+            return '''<li class="active"><span>%s</span></li>''' % text
 
-            # Note: the number of items on this page can be less than
-            #       items_per_page if the last page is not full
-            self.first_item = max(0, (self.item_count) - (self.page *
-                                                          items_per_page))
-            self.last_item = ((self.item_count - 1) - items_per_page *
-                              (self.page - 1))
-
-            self.items = list(self.collection[self.first_item:self.last_item + 1])
-
-            # Links to previous and next page
-            if self.page > self.first_page:
-                self.previous_page = self.page - 1
-            else:
-                self.previous_page = None
-
-            if self.page < self.last_page:
-                self.next_page = self.page + 1
-            else:
-                self.next_page = None
-
-        # No items available
+        if not item['href'] or item['type'] == 'span':
+            if item['attrs']:
+                text = paginate.make_html_tag('span', **item['attrs']) + text + '</span>'
         else:
-            self.first_page = None
-            self.page_count = 0
-            self.last_page = None
-            self.first_item = None
-            self.last_item = None
-            self.previous_page = None
-            self.next_page = None
-            self.items = []
-
-        # This is a subclass of the 'list' type. Initialise the list now.
-        list.__init__(self, reversed(self.items))
+            target_url = item['href']
+            text =  paginate.make_html_tag('a', text=text, href=target_url, **item['attrs'])
+        return '''<li>%s</li>''' % text
--- a/kallithea/lib/paster_commands/template.ini.mako	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/paster_commands/template.ini.mako	Thu Feb 06 01:19:23 2020 +0100
@@ -220,7 +220,7 @@
 <%text>## used, which is correct in many cases but for example not when using uwsgi.</%text>
 <%text>## If you change this setting, you should reinstall the Git hooks via</%text>
 <%text>## Admin > Settings > Remap and Rescan.</%text>
-# git_hook_interpreter = /srv/kallithea/venv/bin/python2
+# git_hook_interpreter = /srv/kallithea/venv/bin/python3
 %if git_hook_interpreter:
 git_hook_interpreter = ${git_hook_interpreter}
 %endif
--- a/kallithea/lib/pidlock.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/pidlock.py	Thu Feb 06 01:19:23 2020 +0100
@@ -137,6 +137,6 @@
         dir_, file_ = os.path.split(pidfile)
         if not os.path.isdir(dir_):
             os.makedirs(dir_)
-        with open(self.pidfile, 'wb') as f:
+        with open(self.pidfile, 'w') as f:
             f.write(lockname)
         self.held = True
--- a/kallithea/lib/pygmentsutils.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/pygmentsutils.py	Thu Feb 06 01:19:23 2020 +0100
@@ -26,7 +26,6 @@
 """
 
 from collections import defaultdict
-from itertools import ifilter
 
 from pygments import lexers
 
@@ -59,15 +58,11 @@
     """
     Get list of known indexable filenames from pygment lexer internals
     """
-
     filenames = []
-
-    def likely_filename(s):
-        return s.find('*') == -1 and s.find('[') == -1
-
     for lx, t in sorted(lexers.LEXERS.items()):
-        for f in ifilter(likely_filename, t[-2]):
-            filenames.append(f)
+        for f in t[-2]:
+            if '*' not in f and '[' not in f:
+                filenames.append(f)
 
     return filenames
 
--- a/kallithea/lib/rcmail/message.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/rcmail/message.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,7 +25,7 @@
 
     @property
     def data(self):
-        if isinstance(self._data, basestring):
+        if isinstance(self._data, str):
             return self._data
         self._data = self._data.read()
         return self._data
--- a/kallithea/lib/rcmail/response.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/rcmail/response.py	Thu Feb 06 01:19:23 2020 +0100
@@ -87,7 +87,7 @@
     def __delitem__(self, key):
         del self.headers[normalize_header(key)]
 
-    def __nonzero__(self):
+    def __bool__(self):
         return self.body is not None or len(self.headers) > 0 or len(self.parts) > 0
 
     def keys(self):
@@ -339,20 +339,20 @@
 
     try:
         out = MIMEPart(ctype, **params)
-    except TypeError as exc:  # pragma: no cover
+    except TypeError as e:  # pragma: no cover
         raise EncodingError("Content-Type malformed, not allowed: %r; "
-                            "%r (Python ERROR: %s" %
-                            (ctype, params, exc.message))
+                            "%r (Python ERROR: %s)" %
+                            (ctype, params, e.args[0]))
 
     for k in mail.keys():
         if k in ADDRESS_HEADERS_WHITELIST:
-            out[k.encode('ascii')] = header_to_mime_encoding(
+            out[k] = header_to_mime_encoding(
                                          mail[k],
                                          not_email=False,
                                          separator=separator
                                      )
         else:
-            out[k.encode('ascii')] = header_to_mime_encoding(
+            out[k] = header_to_mime_encoding(
                                          mail[k],
                                          not_email=True
                                     )
@@ -422,7 +422,7 @@
         return ""
 
     encoder = Charset(DEFAULT_ENCODING)
-    if type(value) == list:
+    if isinstance(value, list):
         return separator.join(properly_encode_header(
             v, encoder, not_email) for v in value)
     else:
@@ -443,12 +443,12 @@
     check different, then change this.
     """
     try:
-        return value.encode("ascii")
-    except UnicodeEncodeError:
+        value.encode("ascii")
+        return value
+    except UnicodeError:
         if not not_email and VALUE_IS_EMAIL_ADDRESS(value):
             # this could have an email address, make sure we don't screw it up
             name, address = parseaddr(value)
-            return '"%s" <%s>' % (
-                encoder.header_encode(name.encode("utf-8")), address)
+            return '"%s" <%s>' % (encoder.header_encode(name), address)
 
-        return encoder.header_encode(value.encode("utf-8"))
+        return encoder.header_encode(value)
--- a/kallithea/lib/rcmail/smtp_mailer.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/rcmail/smtp_mailer.py	Thu Feb 06 01:19:23 2020 +0100
@@ -64,7 +64,7 @@
     def send(self, recipients=None, subject='', body='', html='',
              attachment_files=None, headers=None):
         recipients = recipients or []
-        if isinstance(recipients, basestring):
+        if isinstance(recipients, str):
             recipients = [recipients]
         if headers is None:
             headers = {}
--- a/kallithea/lib/recaptcha.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/recaptcha.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 import json
-import urllib
-import urllib2
+import urllib.parse
+import urllib.request
 
 
 class RecaptchaResponse(object):
@@ -26,17 +26,17 @@
         return RecaptchaResponse(is_valid=False, error_code='incorrect-captcha-sol')
 
     def encode_if_necessary(s):
-        if isinstance(s, unicode):
+        if isinstance(s, str):
             return s.encode('utf-8')
         return s
 
-    params = urllib.urlencode({
+    params = urllib.parse.urlencode({
         'secret': encode_if_necessary(private_key),
         'remoteip': encode_if_necessary(remoteip),
         'response': encode_if_necessary(g_recaptcha_response),
-    })
+    }).encode('ascii')
 
-    req = urllib2.Request(
+    req = urllib.request.Request(
         url="https://www.google.com/recaptcha/api/siteverify",
         data=params,
         headers={
@@ -45,7 +45,7 @@
         }
     )
 
-    httpresp = urllib2.urlopen(req)
+    httpresp = urllib.request.urlopen(req)
     return_values = json.loads(httpresp.read())
     httpresp.close()
 
--- a/kallithea/lib/ssh.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/ssh.py	Thu Feb 06 01:19:23 2020 +0100
@@ -21,12 +21,14 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import binascii
+import base64
 import logging
 import re
 
 from tg.i18n import ugettext as _
 
+from kallithea.lib.utils2 import ascii_bytes, ascii_str
+
 
 log = logging.getLogger(__name__)
 
@@ -42,37 +44,39 @@
     >>> parse_pub_key('')
     Traceback (most recent call last):
     ...
-    SshKeyParseError: SSH key is missing
+    kallithea.lib.ssh.SshKeyParseError: SSH key is missing
     >>> parse_pub_key('''AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ''')
     Traceback (most recent call last):
     ...
-    SshKeyParseError: Incorrect SSH key - it must have both a key type and a base64 part
+    kallithea.lib.ssh.SshKeyParseError: Incorrect SSH key - it must have both a key type and a base64 part, like 'ssh-rsa ASRNeaZu4FA...xlJp='
     >>> parse_pub_key('''abc AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ''')
     Traceback (most recent call last):
     ...
-    SshKeyParseError: Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'
+    kallithea.lib.ssh.SshKeyParseError: Incorrect SSH key - it must start with 'ssh-(rsa|dss|ed25519)'
     >>> parse_pub_key('''ssh-rsa  AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ''')
     Traceback (most recent call last):
     ...
-    SshKeyParseError: Incorrect SSH key - failed to decode base64 part 'AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ'
+    kallithea.lib.ssh.SshKeyParseError: Incorrect SSH key - failed to decode base64 part 'AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ'
     >>> parse_pub_key('''ssh-rsa  AAAAB2NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ==''')
     Traceback (most recent call last):
     ...
-    SshKeyParseError: Incorrect SSH key - base64 part is not 'ssh-rsa' as claimed but 'csh-rsa'
+    kallithea.lib.ssh.SshKeyParseError: Incorrect SSH key - base64 part is not 'ssh-rsa' as claimed but 'csh-rsa'
     >>> parse_pub_key('''ssh-rsa  AAAAB3NzaC1yc2EAAAA'LVGhpcyBpcyBmYWtlIQ''')
     Traceback (most recent call last):
     ...
-    SshKeyParseError: Incorrect SSH key - unexpected characters in base64 part "AAAAB3NzaC1yc2EAAAA'LVGhpcyBpcyBmYWtlIQ"
+    kallithea.lib.ssh.SshKeyParseError: Incorrect SSH key - unexpected characters in base64 part "AAAAB3NzaC1yc2EAAAA'LVGhpcyBpcyBmYWtlIQ"
     >>> parse_pub_key(''' ssh-rsa  AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ== and a comment
     ... ''')
-    ('ssh-rsa', '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x0bThis is fake!', 'and a comment\n')
+    ('ssh-rsa', b'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x0bThis is fake!', 'and a comment\n')
+    >>> parse_pub_key('''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP1NA2kBQIKe74afUXmIWD9ByDYQJqUwW44Y4gJOBRuo''')
+    ('ssh-ed25519', b'\x00\x00\x00\x0bssh-ed25519\x00\x00\x00 \xfdM\x03i\x01@\x82\x9e\xef\x86\x9fQy\x88X?A\xc86\x10&\xa50[\x8e\x18\xe2\x02N\x05\x1b\xa8', '')
     """
     if not ssh_key:
         raise SshKeyParseError(_("SSH key is missing"))
 
     parts = ssh_key.split(None, 2)
     if len(parts) < 2:
-        raise SshKeyParseError(_("Incorrect SSH key - it must have both a key type and a base64 part"))
+        raise SshKeyParseError(_("Incorrect SSH key - it must have both a key type and a base64 part, like 'ssh-rsa ASRNeaZu4FA...xlJp='"))
 
     keytype, keyvalue, comment = (parts + [''])[:3]
     if keytype not in ('ssh-rsa', 'ssh-dss', 'ssh-ed25519'):
@@ -82,19 +86,31 @@
         raise SshKeyParseError(_("Incorrect SSH key - unexpected characters in base64 part %r") % keyvalue)
 
     try:
-        decoded = keyvalue.decode('base64')
-    except binascii.Error:
+        key_bytes = base64.b64decode(keyvalue)
+    except base64.binascii.Error:
         raise SshKeyParseError(_("Incorrect SSH key - failed to decode base64 part %r") % keyvalue)
 
-    if not decoded.startswith('\x00\x00\x00\x07' + str(keytype) + '\x00'):
-        raise SshKeyParseError(_("Incorrect SSH key - base64 part is not %r as claimed but %r") % (str(keytype), str(decoded[4:].split('\0', 1)[0])))
+    if not key_bytes.startswith(b'\x00\x00\x00%c%s\x00' % (len(keytype), ascii_bytes(keytype))):
+        raise SshKeyParseError(_("Incorrect SSH key - base64 part is not %r as claimed but %r") % (keytype, ascii_str(key_bytes[4:].split(b'\0', 1)[0])))
 
-    return keytype, decoded, comment
+    return keytype, key_bytes, comment
 
 
 SSH_OPTIONS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
 
 
+def _safe_check(s, rec = re.compile('^[a-zA-Z0-9+/]+={0,2}$')):
+    """Return true if s really has the right content for base64 encoding and only contains safe characters
+    >>> _safe_check('asdf')
+    True
+    >>> _safe_check('as df')
+    False
+    >>> _safe_check('AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ==')
+    True
+    """
+    return rec.match(s) is not None
+
+
 def authorized_keys_line(kallithea_cli_path, config_file, key):
     """
     Return a line as it would appear in .authorized_keys
@@ -107,11 +123,14 @@
     'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="/srv/kallithea/venv/bin/kallithea-cli ssh-serve -c /srv/kallithea/my.ini 7 17" ssh-rsa AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ==\\n'
     """
     try:
-        keytype, decoded, comment = parse_pub_key(key.public_key)
+        keytype, key_bytes, comment = parse_pub_key(key.public_key)
     except SshKeyParseError:
         return '# Invalid Kallithea SSH key: %s %s\n' % (key.user.user_id, key.user_ssh_key_id)
-    mimekey = decoded.encode('base64').replace('\n', '')
+    base64_key = ascii_str(base64.b64encode(key_bytes))
+    assert '\n' not in base64_key
+    if not _safe_check(base64_key):
+        return '# Invalid Kallithea SSH key - bad base64 encoding: %s %s\n' % (key.user.user_id, key.user_ssh_key_id)
     return '%s,command="%s ssh-serve -c %s %s %s" %s %s\n' % (
         SSH_OPTIONS, kallithea_cli_path, config_file,
         key.user.user_id, key.user_ssh_key_id,
-        keytype, mimekey)
+        keytype, base64_key)
--- a/kallithea/lib/timerproxy.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/timerproxy.py	Thu Feb 06 01:19:23 2020 +0100
@@ -20,7 +20,7 @@
 
 log = logging.getLogger('timerproxy')
 
-BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(30, 38)
 
 
 def color_sql(sql):
--- a/kallithea/lib/utils.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/utils.py	Thu Feb 06 01:19:23 2020 +0100
@@ -33,19 +33,22 @@
 import traceback
 from distutils.version import StrictVersion
 
-import beaker
-from beaker.cache import _cache_decorate
+import beaker.cache
+import mercurial.config
+import mercurial.ui
 from tg.i18n import ugettext as _
 
+import kallithea.config.conf
 from kallithea.lib.exceptions import HgsubversionImportError
-from kallithea.lib.utils2 import get_current_authuser, safe_str, safe_unicode
-from kallithea.lib.vcs.exceptions import VCSError
+from kallithea.lib.utils2 import ascii_bytes, aslist, get_current_authuser, safe_bytes, safe_str
+from kallithea.lib.vcs.backends.git.repository import GitRepository
+from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
+from kallithea.lib.vcs.conf import settings
+from kallithea.lib.vcs.exceptions import RepositoryError, VCSError
 from kallithea.lib.vcs.utils.fakemod import create_module
 from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.utils.hgcompat import config, ui
 from kallithea.model import meta
 from kallithea.model.db import RepoGroup, Repository, Setting, Ui, User, UserGroup, UserLog
-from kallithea.model.repo_group import RepoGroupModel
 
 
 log = logging.getLogger(__name__)
@@ -102,7 +105,6 @@
         rest = '/' + rest_
     repo_id = _get_permanent_id(first)
     if repo_id is not None:
-        from kallithea.model.db import Repository
         repo = Repository.get(repo_id)
         if repo is not None:
             return repo.repo_name + rest
@@ -130,7 +132,7 @@
 
     if getattr(user, 'user_id', None):
         user_obj = User.get(user.user_id)
-    elif isinstance(user, basestring):
+    elif isinstance(user, str):
         user_obj = User.get_by_username(user)
     else:
         raise Exception('You have to provide a user object or a username')
@@ -138,7 +140,7 @@
     if getattr(repo, 'repo_id', None):
         repo_obj = Repository.get(repo.repo_id)
         repo_name = repo_obj.repo_name
-    elif isinstance(repo, basestring):
+    elif isinstance(repo, str):
         repo_name = repo.lstrip('/')
         repo_obj = Repository.get_by_repo_name(repo_name)
     else:
@@ -148,7 +150,7 @@
     user_log = UserLog()
     user_log.user_id = user_obj.user_id
     user_log.username = user_obj.username
-    user_log.action = safe_unicode(action)
+    user_log.action = action
 
     user_log.repository = repo_obj
     user_log.repository_name = repo_name
@@ -158,7 +160,7 @@
     meta.Session().add(user_log)
 
     log.info('Logging action:%s on %s by user:%s ip:%s',
-             action, safe_unicode(repo), user_obj, ipaddr)
+             action, repo, user_obj, ipaddr)
     if commit:
         meta.Session().commit()
 
@@ -172,7 +174,7 @@
     """
 
     # remove ending slash for better results
-    path = safe_str(path.rstrip(os.sep))
+    path = path.rstrip(os.sep)
     log.debug('now scanning in %s', path)
 
     def isdir(*n):
@@ -225,7 +227,6 @@
 def is_valid_repo_uri(repo_type, url, ui):
     """Check if the url seems like a valid remote repo location - raise an Exception if any problems"""
     if repo_type == 'hg':
-        from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
         if url.startswith('http') or url.startswith('ssh'):
             # initially check if it's at least the proper URL
             # or does it pass basic auth
@@ -243,7 +244,6 @@
             raise Exception('URI %s not allowed' % (url,))
 
     elif repo_type == 'git':
-        from kallithea.lib.vcs.backends.git.repository import GitRepository
         if url.startswith('http') or url.startswith('git'):
             # initially check if it's at least the proper URL
             # or does it pass basic auth
@@ -269,7 +269,7 @@
     :return True: if given path is a valid repository
     """
     # TODO: paranoid security checks?
-    full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
+    full_path = os.path.join(base_path, repo_name)
 
     try:
         scm_ = get_scm(full_path)
@@ -287,7 +287,7 @@
     :param repo_name:
     :param base_path:
     """
-    full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
+    full_path = os.path.join(base_path, repo_group_name)
 
     # check if it's not a repo
     if is_valid_repo(repo_group_name, base_path):
@@ -322,49 +322,46 @@
                 'ui', 'web', ]
 
 
-def make_ui(repo_path=None, clear_session=True):
+def make_ui(repo_path=None):
     """
     Create an Mercurial 'ui' object based on database Ui settings, possibly
     augmenting with content from a hgrc file.
     """
-    baseui = ui.ui()
+    baseui = mercurial.ui.ui()
 
     # clean the baseui object
-    baseui._ocfg = config.config()
-    baseui._ucfg = config.config()
-    baseui._tcfg = config.config()
+    baseui._ocfg = mercurial.config.config()
+    baseui._ucfg = mercurial.config.config()
+    baseui._tcfg = mercurial.config.config()
 
     sa = meta.Session()
     for ui_ in sa.query(Ui).all():
         if ui_.ui_active:
-            ui_val = '' if ui_.ui_value is None else safe_str(ui_.ui_value)
             log.debug('config from db: [%s] %s=%r', ui_.ui_section,
-                      ui_.ui_key, ui_val)
-            baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
-                             ui_val)
-    if clear_session:
-        meta.Session.remove()
+                      ui_.ui_key, ui_.ui_value)
+            baseui.setconfig(ascii_bytes(ui_.ui_section), ascii_bytes(ui_.ui_key),
+                             b'' if ui_.ui_value is None else safe_bytes(ui_.ui_value))
 
     # force set push_ssl requirement to False, Kallithea handles that
-    baseui.setconfig('web', 'push_ssl', False)
-    baseui.setconfig('web', 'allow_push', '*')
+    baseui.setconfig(b'web', b'push_ssl', False)
+    baseui.setconfig(b'web', b'allow_push', b'*')
     # prevent interactive questions for ssh password / passphrase
-    ssh = baseui.config('ui', 'ssh', default='ssh')
-    baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
+    ssh = baseui.config(b'ui', b'ssh', default=b'ssh')
+    baseui.setconfig(b'ui', b'ssh', b'%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
     # push / pull hooks
-    baseui.setconfig('hooks', 'changegroup.kallithea_log_push_action', 'python:kallithea.lib.hooks.log_push_action')
-    baseui.setconfig('hooks', 'outgoing.kallithea_log_pull_action', 'python:kallithea.lib.hooks.log_pull_action')
+    baseui.setconfig(b'hooks', b'changegroup.kallithea_log_push_action', b'python:kallithea.lib.hooks.log_push_action')
+    baseui.setconfig(b'hooks', b'outgoing.kallithea_log_pull_action', b'python:kallithea.lib.hooks.log_pull_action')
 
     if repo_path is not None:
         hgrc_path = os.path.join(repo_path, '.hg', 'hgrc')
         if os.path.isfile(hgrc_path):
             log.debug('reading hgrc from %s', hgrc_path)
-            cfg = config.config()
-            cfg.read(hgrc_path)
+            cfg = mercurial.config.config()
+            cfg.read(safe_bytes(hgrc_path))
             for section in ui_sections:
                 for k, v in cfg.items(section):
                     log.debug('config from file: [%s] %s=%s', section, k, v)
-                    baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
+                    baseui.setconfig(ascii_bytes(section), ascii_bytes(k), safe_bytes(v))
         else:
             log.debug('hgrc file is not present at %s, skipping...', hgrc_path)
 
@@ -377,12 +374,9 @@
 
     :param config:
     """
-    try:
-        hgsettings = Setting.get_app_settings()
-        for k, v in hgsettings.items():
-            config[k] = v
-    finally:
-        meta.Session.remove()
+    hgsettings = Setting.get_app_settings()
+    for k, v in hgsettings.items():
+        config[k] = v
 
 
 def set_vcs_config(config):
@@ -391,16 +385,14 @@
 
     :param config: kallithea.CONFIG
     """
-    from kallithea.lib.vcs import conf
-    from kallithea.lib.utils2 import aslist
-    conf.settings.BACKENDS = {
+    settings.BACKENDS = {
         'hg': 'kallithea.lib.vcs.backends.hg.MercurialRepository',
         'git': 'kallithea.lib.vcs.backends.git.GitRepository',
     }
 
-    conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
-    conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
-    conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
+    settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
+    settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
+    settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
                                                         'utf-8'), sep=',')
 
 
@@ -410,13 +402,11 @@
 
     :param config: kallithea.CONFIG
     """
-    from kallithea.config import conf
-
     log.debug('adding extra into INDEX_EXTENSIONS')
-    conf.INDEX_EXTENSIONS.extend(re.split(r'\s+', config.get('index.extensions', '')))
+    kallithea.config.conf.INDEX_EXTENSIONS.extend(re.split(r'\s+', config.get('index.extensions', '')))
 
     log.debug('adding extra into INDEX_FILENAMES')
-    conf.INDEX_FILENAMES.extend(re.split(r'\s+', config.get('index.filenames', '')))
+    kallithea.config.conf.INDEX_FILENAMES.extend(re.split(r'\s+', config.get('index.filenames', '')))
 
 
 def map_groups(path):
@@ -427,6 +417,7 @@
 
     :param paths: full path to repository
     """
+    from kallithea.model.repo_group import RepoGroupModel
     sa = meta.Session()
     groups = path.split(Repository.url_sep())
     parent = None
@@ -459,14 +450,14 @@
     return group
 
 
-def repo2db_mapper(initial_repo_list, remove_obsolete=False,
+def repo2db_mapper(initial_repo_dict, remove_obsolete=False,
                    install_git_hooks=False, user=None, overwrite_git_hooks=False):
     """
-    maps all repos given in initial_repo_list, non existing repositories
+    maps all repos given in initial_repo_dict, non existing repositories
     are created, if remove_obsolete is True it also check for db entries
-    that are not in initial_repo_list and removes them.
+    that are not in initial_repo_dict and removes them.
 
-    :param initial_repo_list: list of repositories found by scanning methods
+    :param initial_repo_dict: mapping with repositories found by scanning methods
     :param remove_obsolete: check for obsolete entries in database
     :param install_git_hooks: if this is True, also check and install git hook
         for a repo if missing
@@ -487,10 +478,9 @@
     enable_downloads = defs.get('repo_enable_downloads')
     private = defs.get('repo_private')
 
-    for name, repo in initial_repo_list.items():
+    for name, repo in initial_repo_dict.items():
         group = map_groups(name)
-        unicode_name = safe_unicode(name)
-        db_repo = repo_model.get_by_repo_name(unicode_name)
+        db_repo = repo_model.get_by_repo_name(name)
         # found repo that is on filesystem not in Kallithea database
         if not db_repo:
             log.info('repository %s not found, creating now', name)
@@ -526,9 +516,8 @@
 
     removed = []
     # remove from database those repositories that are not in the filesystem
-    unicode_initial_repo_list = set(safe_unicode(name) for name in initial_repo_list)
     for repo in sa.query(Repository).all():
-        if repo.repo_name not in unicode_initial_repo_list:
+        if repo.repo_name not in initial_repo_dict:
             if remove_obsolete:
                 log.debug("Removing non-existing repository found in db `%s`",
                           repo.repo_name)
@@ -544,9 +533,6 @@
 
 
 def load_rcextensions(root_path):
-    import kallithea
-    from kallithea.config import conf
-
     path = os.path.join(root_path, 'rcextensions', '__init__.py')
     if os.path.isfile(path):
         rcext = create_module('rc', path)
@@ -554,17 +540,17 @@
         log.debug('Found rcextensions now loading %s...', rcext)
 
         # Additional mappings that are not present in the pygments lexers
-        conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
+        kallithea.config.conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
 
         # OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
 
         if getattr(EXT, 'INDEX_EXTENSIONS', []):
             log.debug('settings custom INDEX_EXTENSIONS')
-            conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
+            kallithea.config.conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
 
         # ADDITIONAL MAPPINGS
         log.debug('adding extra into INDEX_EXTENSIONS')
-        conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
+        kallithea.config.conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
 
         # auto check if the module is not missing any data, set to default if is
         # this will help autoupdate new feature of rcext module
@@ -585,28 +571,33 @@
     Checks what version of git is installed on the system, and raise a system exit
     if it's too old for Kallithea to work properly.
     """
-    from kallithea import BACKENDS
-    from kallithea.lib.vcs.backends.git.repository import GitRepository
-    from kallithea.lib.vcs.conf import settings
-
-    if 'git' not in BACKENDS:
+    if 'git' not in kallithea.BACKENDS:
         return None
 
     if not settings.GIT_EXECUTABLE_PATH:
         log.warning('No git executable configured - check "git_path" in the ini file.')
         return None
 
-    stdout, stderr = GitRepository._run_git_command(['--version'], _bare=True,
-                                                    _safe=True)
+    try:
+        stdout, stderr = GitRepository._run_git_command(['--version'])
+    except RepositoryError as e:
+        # message will already have been logged as error
+        log.warning('No working git executable found - check "git_path" in the ini file.')
+        return None
 
     if stderr:
-        log.warning('Error/stderr from "%s --version": %r', settings.GIT_EXECUTABLE_PATH, stderr)
+        log.warning('Error/stderr from "%s --version":\n%s', settings.GIT_EXECUTABLE_PATH, safe_str(stderr))
 
-    m = re.search(r"\d+.\d+.\d+", stdout)
+    if not stdout:
+        log.warning('No working git executable found - check "git_path" in the ini file.')
+        return None
+
+    output = safe_str(stdout).strip()
+    m = re.search(r"\d+.\d+.\d+", output)
     if m:
         ver = StrictVersion(m.group(0))
         log.debug('Git executable: "%s", version %s (parsed from: "%s")',
-                  settings.GIT_EXECUTABLE_PATH, ver, stdout.strip())
+                  settings.GIT_EXECUTABLE_PATH, ver, output)
         if ver < git_req_ver:
             log.error('Kallithea detected %s version %s, which is too old '
                       'for the system to function properly. '
@@ -618,8 +609,8 @@
             sys.exit(1)
     else:
         ver = StrictVersion('0.0.0')
-        log.warning('Error finding version number in "%s --version" stdout: %r',
-                    settings.GIT_EXECUTABLE_PATH, stdout.strip())
+        log.warning('Error finding version number in "%s --version" stdout:\n%s',
+                    settings.GIT_EXECUTABLE_PATH, output)
 
     return ver
 
@@ -628,36 +619,6 @@
 # CACHE RELATED METHODS
 #===============================================================================
 
-# set cache regions for beaker so celery can utilise it
-def setup_cache_regions(settings):
-    # Create dict with just beaker cache configs with prefix stripped
-    cache_settings = {'regions': None}
-    prefix = 'beaker.cache.'
-    for key in settings:
-        if key.startswith(prefix):
-            name = key[len(prefix):]
-            cache_settings[name] = settings[key]
-    # Find all regions, apply defaults, and apply to beaker
-    if cache_settings['regions']:
-        for region in cache_settings['regions'].split(','):
-            region = region.strip()
-            prefix = region + '.'
-            region_settings = {}
-            for key in cache_settings:
-                if key.startswith(prefix):
-                    name = key[len(prefix):]
-                    region_settings[name] = cache_settings[key]
-            region_settings.setdefault('expire',
-                                       cache_settings.get('expire', '60'))
-            region_settings.setdefault('lock_dir',
-                                       cache_settings.get('lock_dir'))
-            region_settings.setdefault('data_dir',
-                                       cache_settings.get('data_dir'))
-            region_settings.setdefault('type',
-                                       cache_settings.get('type', 'memory'))
-            beaker.cache.cache_regions[region] = region_settings
-
-
 def conditional_cache(region, prefix, condition, func):
     """
 
@@ -680,6 +641,6 @@
     if condition:
         log.debug('conditional_cache: True, wrapping call of '
                   'func: %s into %s region cache' % (region, func))
-        wrapped = _cache_decorate((prefix,), None, None, region)(func)
+        wrapped = beaker.cache._cache_decorate((prefix,), None, None, region)(func)
 
     return wrapped
--- a/kallithea/lib/utils2.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/utils2.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,18 +31,19 @@
 
 import binascii
 import datetime
+import json
 import os
 import pwd
 import re
 import time
-import urllib
+import urllib.parse
 
 import urlobject
 from tg.i18n import ugettext as _
 from tg.i18n import ungettext
 from webhelpers2.text import collapse, remove_formatting, strip_tags
 
-from kallithea.lib.compat import json
+from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, safe_bytes, safe_str  # re-export
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 
 
@@ -71,7 +72,7 @@
     :param sep:
     :param strip:
     """
-    if isinstance(obj, (basestring)):
+    if isinstance(obj, (str)):
         lst = obj.split(sep)
         if strip:
             lst = [v.strip() for v in lst]
@@ -98,14 +99,12 @@
     :rtype: str
     :return: converted line according to mode
     """
-    from string import replace
-
     if mode == 0:
-        line = replace(line, '\r\n', '\n')
-        line = replace(line, '\r', '\n')
+        line = line.replace('\r\n', '\n')
+        line = line.replace('\r', '\n')
     elif mode == 1:
-        line = replace(line, '\r\n', '\r')
-        line = replace(line, '\n', '\r')
+        line = line.replace('\r\n', '\r')
+        line = line.replace('\n', '\r')
     elif mode == 2:
         line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
     return line
@@ -142,7 +141,7 @@
         unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
     """
     # Hexadecimal certainly qualifies as URL-safe.
-    return binascii.hexlify(os.urandom(20))
+    return ascii_str(binascii.hexlify(os.urandom(20)))
 
 
 def safe_int(val, default=None):
@@ -153,104 +152,13 @@
     :param val:
     :param default:
     """
-
     try:
         val = int(val)
     except (ValueError, TypeError):
         val = default
-
     return val
 
 
-def safe_unicode(str_, from_encoding=None):
-    """
-    safe unicode function. Does few trick to turn str_ into unicode
-
-    In case of UnicodeDecode error we try to return it with encoding detected
-    by chardet library if it fails fallback to unicode with errors replaced
-
-    :param str_: string to decode
-    :rtype: unicode
-    :returns: unicode object
-    """
-    if isinstance(str_, unicode):
-        return str_
-
-    if not from_encoding:
-        import kallithea
-        DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
-                                                        'utf-8'), sep=',')
-        from_encoding = DEFAULT_ENCODINGS
-
-    if not isinstance(from_encoding, (list, tuple)):
-        from_encoding = [from_encoding]
-
-    try:
-        return unicode(str_)
-    except UnicodeDecodeError:
-        pass
-
-    for enc in from_encoding:
-        try:
-            return unicode(str_, enc)
-        except UnicodeDecodeError:
-            pass
-
-    try:
-        import chardet
-        encoding = chardet.detect(str_)['encoding']
-        if encoding is None:
-            raise Exception()
-        return str_.decode(encoding)
-    except (ImportError, UnicodeDecodeError, Exception):
-        return unicode(str_, from_encoding[0], 'replace')
-
-
-def safe_str(unicode_, to_encoding=None):
-    """
-    safe str function. Does few trick to turn unicode_ into string
-
-    In case of UnicodeEncodeError we try to return it with encoding detected
-    by chardet library if it fails fallback to string with errors replaced
-
-    :param unicode_: unicode to encode
-    :rtype: str
-    :returns: str object
-    """
-
-    # if it's not basestr cast to str
-    if not isinstance(unicode_, basestring):
-        return str(unicode_)
-
-    if isinstance(unicode_, str):
-        return unicode_
-
-    if not to_encoding:
-        import kallithea
-        DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
-                                                        'utf-8'), sep=',')
-        to_encoding = DEFAULT_ENCODINGS
-
-    if not isinstance(to_encoding, (list, tuple)):
-        to_encoding = [to_encoding]
-
-    for enc in to_encoding:
-        try:
-            return unicode_.encode(enc)
-        except UnicodeEncodeError:
-            pass
-
-    try:
-        import chardet
-        encoding = chardet.detect(unicode_)['encoding']
-        if encoding is None:
-            raise UnicodeEncodeError()
-
-        return unicode_.encode(encoding)
-    except (ImportError, UnicodeEncodeError):
-        return unicode_.encode(to_encoding[0], 'replace')
-
-
 def remove_suffix(s, suffix):
     if s.endswith(suffix):
         s = s[:-1 * len(suffix)]
@@ -271,8 +179,8 @@
 
     :param prevdate: datetime object
     :param show_short_version: if it should approximate the date and return a shorter string
-    :rtype: unicode
-    :returns: unicode words describing age
+    :rtype: str
+    :returns: str words describing age
     """
     now = now or datetime.datetime.now()
     order = ['year', 'month', 'day', 'hour', 'minute', 'second']
@@ -370,11 +278,11 @@
     Removes user:password from given url string
 
     :param uri:
-    :rtype: unicode
+    :rtype: str
     :returns: filtered list of strings
     """
     if not uri:
-        return ''
+        return []
 
     proto = ''
 
@@ -394,7 +302,7 @@
     else:
         host, port = uri[:cred_pos], uri[cred_pos + 1:]
 
-    return filter(None, [proto, host, port])
+    return [_f for _f in [proto, host, port] if _f]
 
 
 def credentials_filter(uri):
@@ -414,19 +322,19 @@
 
 def get_clone_url(clone_uri_tmpl, prefix_url, repo_name, repo_id, username=None):
     parsed_url = urlobject.URLObject(prefix_url)
-    prefix = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
+    prefix = urllib.parse.unquote(parsed_url.path.rstrip('/'))
     try:
         system_user = pwd.getpwuid(os.getuid()).pw_name
     except Exception: # TODO: support all systems - especially Windows
         system_user = 'kallithea' # hardcoded default value ...
     args = {
         'scheme': parsed_url.scheme,
-        'user': safe_unicode(urllib.quote(safe_str(username or ''))),
+        'user': urllib.parse.quote(username or ''),
         'netloc': parsed_url.netloc + prefix,  # like "hostname:port/prefix" (with optional ":port" and "/prefix")
         'prefix': prefix, # undocumented, empty or starting with /
         'repo': repo_name,
         'repoid': str(repo_id),
-        'system_user': safe_unicode(system_user),
+        'system_user': system_user,
         'hostname': parsed_url.hostname,
     }
     url = re.sub('{([^{}]+)}', lambda m: args.get(m.group(1), m.group(0)), clone_uri_tmpl)
@@ -436,7 +344,7 @@
     if not url_obj.username:
         url_obj = url_obj.with_username(None)
 
-    return safe_unicode(url_obj)
+    return str(url_obj)
 
 
 def get_changeset_safe(repo, rev):
@@ -468,7 +376,7 @@
 
 def time_to_datetime(tm):
     if tm:
-        if isinstance(tm, basestring):
+        if isinstance(tm, str):
             try:
                 tm = float(tm)
             except ValueError:
@@ -522,6 +430,9 @@
     return str(_url)
 
 
+class HookEnvironmentError(Exception): pass
+
+
 def get_hook_environment():
     """
     Get hook context by deserializing the global KALLITHEA_EXTRAS environment
@@ -533,15 +444,16 @@
     """
 
     try:
-        extras = json.loads(os.environ['KALLITHEA_EXTRAS'])
+        kallithea_extras = os.environ['KALLITHEA_EXTRAS']
     except KeyError:
-        raise Exception("Environment variable KALLITHEA_EXTRAS not found")
+        raise HookEnvironmentError("Environment variable KALLITHEA_EXTRAS not found")
 
-    try:
-        for k in ['username', 'repository', 'scm', 'action', 'ip']:
+    extras = json.loads(kallithea_extras)
+    for k in ['username', 'repository', 'scm', 'action', 'ip', 'config']:
+        try:
             extras[k]
-    except KeyError:
-        raise Exception('Missing key %s in KALLITHEA_EXTRAS %s' % (k, extras))
+        except KeyError:
+            raise HookEnvironmentError('Missing key %s in KALLITHEA_EXTRAS %s' % (k, extras))
 
     return AttributeDict(extras)
 
@@ -573,10 +485,10 @@
     defined, else returns None.
     """
     from tg import tmpl_context
-    if hasattr(tmpl_context, 'authuser'):
-        return tmpl_context.authuser
-
-    return None
+    try:
+        return getattr(tmpl_context, 'authuser', None)
+    except TypeError:  # No object (name: context) has been registered for this thread
+        return None
 
 
 class OptionalAttr(object):
@@ -649,7 +561,7 @@
 
 
 def urlreadable(s, _cleanstringsub=re.compile('[^-a-zA-Z0-9./]+').sub):
-    return _cleanstringsub('_', safe_str(s)).rstrip('_')
+    return _cleanstringsub('_', s).rstrip('_')
 
 
 def recursive_replace(str_, replace=' '):
@@ -690,7 +602,7 @@
 
 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
     while True:
-        ok = raw_input(prompt)
+        ok = input(prompt)
         if ok in ('y', 'ye', 'yes'):
             return True
         if ok in ('n', 'no', 'nop', 'nope'):
--- a/kallithea/lib/vcs/backends/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -9,7 +9,6 @@
     :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 """
 import os
-from pprint import pformat
 
 from kallithea.lib.vcs.conf import settings
 from kallithea.lib.vcs.exceptions import VCSError
@@ -51,7 +50,7 @@
     """
     if alias not in settings.BACKENDS:
         raise VCSError("Given alias '%s' is not recognized! Allowed aliases:\n"
-            "%s" % (alias, pformat(settings.BACKENDS.keys())))
+            "%s" % (alias, '", "'.join(settings.BACKENDS)))
     backend_path = settings.BACKENDS[alias]
     klass = import_class(backend_path)
     return klass
--- a/kallithea/lib/vcs/backends/base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -15,7 +15,7 @@
 from kallithea.lib.vcs.conf import settings
 from kallithea.lib.vcs.exceptions import (
     ChangesetError, EmptyRepositoryError, NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, RepositoryError)
-from kallithea.lib.vcs.utils import author_email, author_name, safe_unicode
+from kallithea.lib.vcs.utils import author_email, author_name
 from kallithea.lib.vcs.utils.helpers import get_dict_for_attrs
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 
@@ -98,10 +98,6 @@
         """
         raise NotImplementedError
 
-    @property
-    def name_unicode(self):
-        return safe_unicode(self.name)
-
     @LazyProperty
     def owner(self):
         raise NotImplementedError
@@ -173,14 +169,9 @@
         """
         raise NotImplementedError
 
-    def __getslice__(self, i, j):
-        """
-        Returns a iterator of sliced repository
-        """
-        for rev in self.revisions[i:j]:
-            yield self.get_changeset(rev)
-
     def __getitem__(self, key):
+        if isinstance(key, slice):
+            return (self.get_changeset(rev) for rev in self.revisions[key])
         return self.get_changeset(key)
 
     def count(self):
@@ -329,9 +320,6 @@
         ``repository``
             repository object within which changeset exists
 
-        ``id``
-            may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
-
         ``raw_id``
             raw changeset representation (i.e. full 40 length sha for git
             backend)
@@ -354,10 +342,10 @@
             combined list of ``Node`` objects
 
         ``author``
-            author of the changeset, as unicode
+            author of the changeset, as str
 
         ``message``
-            message of the changeset, as unicode
+            message of the changeset, as str
 
         ``parents``
             list of parent changesets
@@ -374,10 +362,9 @@
     def __repr__(self):
         return self.__str__()
 
-    def __unicode__(self):
-        return u'%s:%s' % (self.revision, self.short_id)
-
     def __eq__(self, other):
+        if type(self) is not type(other):
+            return False
         return self.raw_id == other.raw_id
 
     def __json__(self, with_file_list=False):
@@ -389,9 +376,9 @@
                 message=self.message,
                 date=self.date,
                 author=self.author,
-                added=[safe_unicode(el.path) for el in self.added],
-                changed=[safe_unicode(el.path) for el in self.changed],
-                removed=[safe_unicode(el.path) for el in self.removed],
+                added=[el.path for el in self.added],
+                changed=[el.path for el in self.changed],
+                removed=[el.path for el in self.removed],
             )
         else:
             return dict(
@@ -424,13 +411,6 @@
         raise NotImplementedError
 
     @LazyProperty
-    def id(self):
-        """
-        Returns string identifying this changeset.
-        """
-        raise NotImplementedError
-
-    @LazyProperty
     def raw_id(self):
         """
         Returns raw string identifying this changeset.
@@ -660,12 +640,12 @@
         """
         Returns dictionary with changeset's attributes and their values.
         """
-        data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
+        data = get_dict_for_attrs(self, ['raw_id', 'short_id',
             'revision', 'date', 'message'])
         data['author'] = {'name': self.author_name, 'email': self.author_email}
-        data['added'] = [safe_unicode(node.path) for node in self.added]
-        data['changed'] = [safe_unicode(node.path) for node in self.changed]
-        data['removed'] = [safe_unicode(node.path) for node in self.removed]
+        data['added'] = [node.path for node in self.added]
+        data['changed'] = [node.path for node in self.changed]
+        data['removed'] = [node.path for node in self.removed]
         return data
 
     @LazyProperty
@@ -936,18 +916,18 @@
                         "at %s" % (node.path, p))
 
         # Check nodes marked as changed
-        missing = set(self.changed)
-        not_changed = set(self.changed)
+        missing = set(node.path for node in self.changed)
+        not_changed = set(node.path for node in self.changed)
         if self.changed and not parents:
-            raise NodeDoesNotExistError(str(self.changed[0].path))
+            raise NodeDoesNotExistError(self.changed[0].path)
         for p in parents:
             for node in self.changed:
                 try:
                     old = p.get_node(node.path)
-                    missing.remove(node)
+                    missing.remove(node.path)
                     # if content actually changed, remove node from unchanged
                     if old.content != node.content:
-                        not_changed.remove(node)
+                        not_changed.remove(node.path)
                 except NodeDoesNotExistError:
                     pass
         if self.changed and missing:
@@ -956,7 +936,7 @@
 
         if self.changed and not_changed:
             raise NodeNotChangedError("Node at %s wasn't actually changed "
-                "since parents' changesets: %s" % (not_changed.pop().path,
+                "since parents' changesets: %s" % (not_changed.pop(),
                     parents)
             )
 
@@ -969,10 +949,10 @@
             for node in self.removed:
                 try:
                     p.get_node(node.path)
-                    really_removed.add(node)
+                    really_removed.add(node.path)
                 except ChangesetError:
                     pass
-        not_removed = set(self.removed) - really_removed
+        not_removed = list(set(node.path for node in self.removed) - really_removed)
         if not_removed:
             raise NodeDoesNotExistError("Cannot remove node at %s from "
                 "following parents: %s" % (not_removed[0], parents))
--- a/kallithea/lib/vcs/backends/git/changeset.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/git/changeset.py	Thu Feb 06 01:19:23 2020 +0100
@@ -11,36 +11,34 @@
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, RepositoryError, VCSError
 from kallithea.lib.vcs.nodes import (
     AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode)
-from kallithea.lib.vcs.utils import date_fromtimestamp, safe_int, safe_str, safe_unicode
+from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, date_fromtimestamp, safe_int, safe_str
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 
 
 class GitChangeset(BaseChangeset):
     """
-    Represents state of the repository at single revision.
+    Represents state of the repository at a revision.
     """
 
     def __init__(self, repository, revision):
         self._stat_modes = {}
         self.repository = repository
-        revision = safe_str(revision)
         try:
-            commit = self.repository._repo[revision]
+            commit = self.repository._repo[ascii_bytes(revision)]
             if isinstance(commit, objects.Tag):
                 revision = safe_str(commit.object[1])
                 commit = self.repository._repo.get_object(commit.object[1])
         except KeyError:
             raise RepositoryError("Cannot get object with id %s" % revision)
-        self.raw_id = revision
-        self.id = self.raw_id
+        self.raw_id = ascii_str(commit.id)
         self.short_id = self.raw_id[:12]
-        self._commit = commit
+        self._commit = commit  # a Dulwich Commmit with .id
         self._tree_id = commit.tree
         self._committer_property = 'committer'
         self._author_property = 'author'
         self._date_property = 'commit_time'
         self._date_tz_property = 'commit_timezone'
-        self.revision = repository.revisions.index(revision)
+        self.revision = repository.revisions.index(self.raw_id)
 
         self.nodes = {}
         self._paths = {}
@@ -51,15 +49,15 @@
 
     @LazyProperty
     def message(self):
-        return safe_unicode(self._commit.message)
+        return safe_str(self._commit.message)
 
     @LazyProperty
     def committer(self):
-        return safe_unicode(getattr(self._commit, self._committer_property))
+        return safe_str(getattr(self._commit, self._committer_property))
 
     @LazyProperty
     def author(self):
-        return safe_unicode(getattr(self._commit, self._author_property))
+        return safe_str(getattr(self._commit, self._author_property))
 
     @LazyProperty
     def date(self):
@@ -80,7 +78,7 @@
     @LazyProperty
     def tags(self):
         _tags = []
-        for tname, tsha in self.repository.tags.iteritems():
+        for tname, tsha in self.repository.tags.items():
             if tsha == self.raw_id:
                 _tags.append(tname)
         return _tags
@@ -91,14 +89,14 @@
         # that might not make sense in Git where branches() is a better match
         # for the basic model
         heads = self.repository._heads(reverse=False)
-        ref = heads.get(self.raw_id)
+        ref = heads.get(self._commit.id)
         if ref:
-            return safe_unicode(ref)
+            return safe_str(ref)
 
     @LazyProperty
     def branches(self):
         heads = self.repository._heads(reverse=True)
-        return [b for b in heads if heads[b] == self.raw_id] # FIXME: Inefficient ... and returning None!
+        return [safe_str(b) for b in heads if heads[b] == self._commit.id] # FIXME: Inefficient ... and returning None!
 
     def _fix_path(self, path):
         """
@@ -110,7 +108,6 @@
         return path
 
     def _get_id_for_path(self, path):
-        path = safe_str(path)
         # FIXME: Please, spare a couple of minutes and make those codes cleaner;
         if path not in self._paths:
             path = path.strip('/')
@@ -124,11 +121,10 @@
             curdir = ''
 
             # initially extract things from root dir
-            for item, stat, id in tree.iteritems():
+            for item, stat, id in tree.items():
+                name = safe_str(item)
                 if curdir:
-                    name = '/'.join((curdir, item))
-                else:
-                    name = item
+                    name = '/'.join((curdir, name))
                 self._paths[name] = id
                 self._stat_modes[name] = stat
 
@@ -138,8 +134,9 @@
                 else:
                     curdir = dir
                 dir_id = None
-                for item, stat, id in tree.iteritems():
-                    if dir == item:
+                for item, stat, id in tree.items():
+                    name = safe_str(item)
+                    if dir == name:
                         dir_id = id
                 if dir_id:
                     # Update tree
@@ -150,17 +147,16 @@
                     raise ChangesetError('%s have not been found' % curdir)
 
                 # cache all items from the given traversed tree
-                for item, stat, id in tree.iteritems():
+                for item, stat, id in tree.items():
+                    name = safe_str(item)
                     if curdir:
-                        name = '/'.join((curdir, item))
-                    else:
-                        name = item
+                        name = '/'.join((curdir, name))
                     self._paths[name] = id
                     self._stat_modes[name] = stat
             if path not in self._paths:
                 raise NodeDoesNotExistError("There is no file nor directory "
                     "at the given path '%s' at revision %s"
-                    % (path, safe_str(self.short_id)))
+                    % (path, self.short_id))
         return self._paths[path]
 
     def _get_kind(self, path):
@@ -185,8 +181,8 @@
         """
         Returns list of parents changesets.
         """
-        return [self.repository.get_changeset(parent)
-                for parent in self._commit.parents]
+        return [self.repository.get_changeset(ascii_str(parent_id))
+                for parent_id in self._commit.parents]
 
     @LazyProperty
     def children(self):
@@ -194,17 +190,15 @@
         Returns list of children changesets.
         """
         rev_filter = settings.GIT_REV_FILTER
-        so, se = self.repository.run_git_command(
+        so = self.repository.run_git_command(
             ['rev-list', rev_filter, '--children']
         )
-
-        children = []
-        pat = re.compile(r'^%s' % self.raw_id)
-        for l in so.splitlines():
-            if pat.match(l):
-                childs = l.split(' ')[1:]
-                children.extend(childs)
-        return [self.repository.get_changeset(cs) for cs in children]
+        return [
+            self.repository.get_changeset(cs)
+            for parts in (l.split(' ') for l in so.splitlines())
+            if parts[0] == self.raw_id
+            for cs in parts[1:]
+        ]
 
     def next(self, branch=None):
         if branch and self.branch != branch:
@@ -243,9 +237,10 @@
                 return cs
 
     def diff(self, ignore_whitespace=True, context=3):
+        # Only used to feed diffstat
         rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
         rev2 = self
-        return ''.join(self.repository.get_diff(rev1, rev2,
+        return b''.join(self.repository.get_diff(rev1, rev2,
                                     ignore_whitespace=ignore_whitespace,
                                     context=context))
 
@@ -254,7 +249,6 @@
         Returns stat mode of the file at the given ``path``.
         """
         # ensure path is traversed
-        path = safe_str(path)
         self._get_id_for_path(path)
         return self._stat_modes[path]
 
@@ -290,17 +284,15 @@
         iterating commits.
         """
         self._get_filectx(path)
-        cs_id = safe_str(self.id)
-        f_path = safe_str(path)
 
         if limit is not None:
             cmd = ['log', '-n', str(safe_int(limit, 0)),
-                   '--pretty=format:%H', '-s', cs_id, '--', f_path]
+                   '--pretty=format:%H', '-s', self.raw_id, '--', path]
 
         else:
             cmd = ['log',
-                   '--pretty=format:%H', '-s', cs_id, '--', f_path]
-        so, se = self.repository.run_git_command(cmd)
+                   '--pretty=format:%H', '-s', self.raw_id, '--', path]
+        so = 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]
 
@@ -312,31 +304,29 @@
         """
         self._get_filectx(path)
         from dulwich.walk import Walker
-        include = [self.id]
+        include = [self.raw_id]
         walker = Walker(self.repository._repo.object_store, include,
                         paths=[path], max_entries=1)
-        return [self.repository.get_changeset(sha)
-                for sha in (x.commit.id for x in walker)]
+        return [self.repository.get_changeset(ascii_str(x.commit.id.decode))
+                for x in walker]
 
     def get_file_annotate(self, path):
         """
         Returns a generator of four element tuples with
             lineno, sha, changeset lazy loader and line
-
-        TODO: This function now uses os underlying 'git' command which is
-        generally not good. Should be replaced with algorithm iterating
-        commits.
         """
-        cmd = ['blame', '-l', '--root', '-r', self.id, '--', path]
+        # TODO: This function now uses os underlying 'git' command which is
+        # generally not good. Should be replaced with algorithm iterating
+        # commits.
+        cmd = ['blame', '-l', '--root', '-r', self.raw_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
-        so, se = self.repository.run_git_command(cmd)
+        so = self.repository.run_git_command(cmd)
 
         for i, blame_line in enumerate(so.split('\n')[:-1]):
-            ln_no = i + 1
             sha, line = re.split(r' ', blame_line, 1)
-            yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
+            yield (i + 1, sha, lambda sha=sha: self.repository.get_changeset(sha), line)
 
     def fill_archive(self, stream=None, kind='tgz', prefix=None,
                      subrepos=False):
@@ -353,12 +343,15 @@
 
         :raise ImproperArchiveTypeError: If given kind is wrong.
         :raise VcsError: If given stream is None
-
         """
-        allowed_kinds = settings.ARCHIVE_SPECS.keys()
+        allowed_kinds = settings.ARCHIVE_SPECS
         if kind not in allowed_kinds:
             raise ImproperArchiveTypeError('Archive kind not supported use one'
-                'of %s' % allowed_kinds)
+                'of %s' % ' '.join(allowed_kinds))
+
+        if stream is None:
+            raise VCSError('You need to pass in a valid stream for filling'
+                           ' with archival data')
 
         if prefix is None:
             prefix = '%s-%s' % (self.repository.name, self.short_id)
@@ -394,6 +387,12 @@
         popen.communicate()
 
     def get_nodes(self, path):
+        """
+        Returns combined ``DirNode`` and ``FileNode`` objects list representing
+        state of changeset at the given ``path``. If node at the given ``path``
+        is not instance of ``DirNode``, ChangesetError would be raised.
+        """
+
         if self._get_kind(path) != NodeKind.DIR:
             raise ChangesetError("Directory does not exist for revision %s at "
                 " '%s'" % (self.revision, path))
@@ -403,16 +402,15 @@
         dirnodes = []
         filenodes = []
         als = self.repository.alias
-        for name, stat, id in tree.iteritems():
+        for name, stat, id in tree.items():
+            obj_path = safe_str(name)
             if path != '':
-                obj_path = '/'.join((path, name))
-            else:
-                obj_path = name
+                obj_path = '/'.join((path, obj_path))
             if objects.S_ISGITLINK(stat):
                 root_tree = self.repository._repo[self._tree_id]
-                cf = ConfigFile.from_file(BytesIO(self.repository._repo.get_object(root_tree['.gitmodules'][1]).data))
-                url = cf.get(('submodule', obj_path), 'url')
-                dirnodes.append(SubModuleNode(obj_path, url=url, changeset=id,
+                cf = ConfigFile.from_file(BytesIO(self.repository._repo.get_object(root_tree[b'.gitmodules'][1]).data))
+                url = ascii_str(cf.get(('submodule', obj_path), 'url'))
+                dirnodes.append(SubModuleNode(obj_path, url=url, changeset=ascii_str(id),
                                               alias=als))
                 continue
 
@@ -434,8 +432,10 @@
         return nodes
 
     def get_node(self, path):
-        if isinstance(path, unicode):
-            path = path.encode('utf-8')
+        """
+        Returns ``Node`` object from the given ``path``. If there is no node at
+        the given ``path``, ``ChangesetError`` would be raised.
+        """
         path = self._fix_path(path)
         if path not in self.nodes:
             try:
@@ -447,9 +447,9 @@
             _GL = lambda m: m and objects.S_ISGITLINK(m)
             if _GL(self._stat_modes.get(path)):
                 tree = self.repository._repo[self._tree_id]
-                cf = ConfigFile.from_file(BytesIO(self.repository._repo.get_object(tree['.gitmodules'][1]).data))
-                url = cf.get(('submodule', path), 'url')
-                node = SubModuleNode(path, url=url, changeset=id_,
+                cf = ConfigFile.from_file(BytesIO(self.repository._repo.get_object(tree[b'.gitmodules'][1]).data))
+                url = ascii_str(cf.get(('submodule', path), 'url'))
+                node = SubModuleNode(path, url=url, changeset=ascii_str(id_),
                                      alias=self.repository.alias)
             else:
                 obj = self.repository._repo.get_object(id_)
@@ -465,7 +465,7 @@
                     node._blob = obj
                 else:
                     raise NodeDoesNotExistError("There is no file nor directory "
-                        "at the given path '%s' at revision %s"
+                        "at the given path: '%s' at revision %s"
                         % (path, self.short_id))
             # cache node
             self.nodes[path] = node
@@ -480,16 +480,6 @@
         return list(added.union(modified).union(deleted))
 
     @LazyProperty
-    def _diff_name_status(self):
-        output = []
-        for parent in self.parents:
-            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)
-
-    @LazyProperty
     def _changes_cache(self):
         added = set()
         modified = set()
@@ -503,15 +493,15 @@
             if isinstance(parent, EmptyChangeset):
                 oid = None
             else:
-                oid = _r[parent.raw_id].tree
-            changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
+                oid = _r[parent._commit.id].tree
+            changes = _r.object_store.tree_changes(oid, _r[self._commit.id].tree)
             for (oldpath, newpath), (_, _), (_, _) in changes:
                 if newpath and oldpath:
-                    modified.add(newpath)
+                    modified.add(safe_str(newpath))
                 elif newpath and not oldpath:
-                    added.add(newpath)
+                    added.add(safe_str(newpath))
                 elif not newpath and oldpath:
-                    deleted.add(oldpath)
+                    deleted.add(safe_str(oldpath))
         return added, modified, deleted
 
     def _get_paths_for_status(self, status):
--- a/kallithea/lib/vcs/backends/git/inmemory.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/git/inmemory.py	Thu Feb 06 01:19:23 2020 +0100
@@ -7,7 +7,7 @@
 
 from kallithea.lib.vcs.backends.base import BaseInMemoryChangeset
 from kallithea.lib.vcs.exceptions import RepositoryError
-from kallithea.lib.vcs.utils import safe_str
+from kallithea.lib.vcs.utils import ascii_str, safe_bytes
 
 
 class GitInMemoryChangeset(BaseInMemoryChangeset):
@@ -39,7 +39,7 @@
         repo = self.repository._repo
         object_store = repo.object_store
 
-        ENCODING = "UTF-8"
+        ENCODING = b"UTF-8"  # TODO: should probably be kept in sync with safe_str/safe_bytes and vcs/conf/settings.py DEFAULT_ENCODINGS
 
         # Create tree and populates it with blobs
         commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or \
@@ -47,7 +47,7 @@
         for node in self.added + self.changed:
             # Compute subdirs if needed
             dirpath, nodename = posixpath.split(node.path)
-            dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
+            dirnames = safe_bytes(dirpath).split(b'/') if dirpath else []
             parent = commit_tree
             ancestors = [('', parent)]
 
@@ -68,13 +68,9 @@
             # for dirnames (in reverse order) [this only applies for nodes from added]
             new_trees = []
 
-            if not node.is_binary:
-                content = node.content.encode(ENCODING)
-            else:
-                content = node.content
-            blob = objects.Blob.from_string(content)
+            blob = objects.Blob.from_string(node.content)
 
-            node_path = node.name.encode(ENCODING)
+            node_path = safe_bytes(node.name)
             if dirnames:
                 # If there are trees which should be created we need to build
                 # them now (in reverse order)
@@ -104,7 +100,7 @@
             for tree in new_trees:
                 object_store.add_object(tree)
         for node in self.removed:
-            paths = node.path.split('/')
+            paths = safe_bytes(node.path).split(b'/')
             tree = commit_tree
             trees = [tree]
             # Traverse deep into the forest...
@@ -117,7 +113,7 @@
                 except KeyError:
                     break
             # Cut down the blob and all rotten trees on the way back...
-            for path, tree in reversed(zip(paths, trees)):
+            for path, tree in reversed(list(zip(paths, trees))):
                 del tree[path]
                 if tree:
                     # This tree still has elements - don't remove it or any
@@ -130,9 +126,9 @@
         commit = objects.Commit()
         commit.tree = commit_tree.id
         commit.parents = [p._commit.id for p in self.parents if p]
-        commit.author = commit.committer = safe_str(author)
+        commit.author = commit.committer = safe_bytes(author)
         commit.encoding = ENCODING
-        commit.message = safe_str(message)
+        commit.message = safe_bytes(message)
 
         # Compute date
         if date is None:
@@ -150,11 +146,10 @@
 
         object_store.add_object(commit)
 
-        ref = 'refs/heads/%s' % branch
+        # Update vcs repository object & recreate dulwich repo
+        ref = b'refs/heads/%s' % safe_bytes(branch)
         repo.refs[ref] = commit.id
-
-        # Update vcs repository object & recreate dulwich repo
-        self.repository.revisions.append(commit.id)
+        self.repository.revisions.append(ascii_str(commit.id))
         # invalidate parsed refs after commit
         self.repository._parsed_refs = self.repository._get_parsed_refs()
         tip = self.repository.get_changeset()
@@ -177,15 +172,15 @@
             return []
 
         def get_tree_for_dir(tree, dirname):
-            for name, mode, id in tree.iteritems():
+            for name, mode, id in tree.items():
                 if name == dirname:
                     obj = self.repository._repo[id]
                     if isinstance(obj, objects.Tree):
                         return obj
                     else:
                         raise RepositoryError("Cannot create directory %s "
-                        "at tree %s as path is occupied and is not a "
-                        "Tree" % (dirname, tree))
+                            "at tree %s as path is occupied and is not a "
+                            "Tree" % (dirname, tree))
             return None
 
         trees = []
--- a/kallithea/lib/vcs/backends/git/repository.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/git/repository.py	Thu Feb 06 01:19:23 2020 +0100
@@ -12,13 +12,15 @@
 import errno
 import logging
 import os
-import posixpath
 import re
 import time
-import urllib
-import urllib2
+import urllib.error
+import urllib.parse
+import urllib.request
 from collections import OrderedDict
 
+import mercurial.url  # import httpbasicauthhandler, httpdigestauthhandler
+import mercurial.util  # import url as hg_url
 from dulwich.config import ConfigFile
 from dulwich.objects import Tag
 from dulwich.repo import NotGitRepository, Repo
@@ -28,8 +30,7 @@
 from kallithea.lib.vcs.conf import settings
 from kallithea.lib.vcs.exceptions import (
     BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError, TagAlreadyExistError, TagDoesNotExistError)
-from kallithea.lib.vcs.utils import date_fromtimestamp, makedate, safe_str, safe_unicode
-from kallithea.lib.vcs.utils.hgcompat import hg_url, httpbasicauthhandler, httpdigestauthhandler
+from kallithea.lib.vcs.utils import ascii_str, date_fromtimestamp, makedate, safe_bytes, safe_str
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.lib.vcs.utils.paths import abspath, get_user_home
 
@@ -53,7 +54,7 @@
     def __init__(self, repo_path, create=False, src_url=None,
                  update_after_clone=False, bare=False):
 
-        self.path = safe_unicode(abspath(repo_path))
+        self.path = abspath(repo_path)
         self.repo = self._get_repo(create, src_url, update_after_clone, bare)
         self.bare = self.repo.bare
 
@@ -97,63 +98,54 @@
         return self._get_all_revisions()
 
     @classmethod
-    def _run_git_command(cls, cmd, **opts):
+    def _run_git_command(cls, cmd, cwd=None):
         """
-        Runs given ``cmd`` as git command and returns tuple
-        (stdout, stderr).
+        Runs given ``cmd`` as git command and returns output bytes in a tuple
+        (stdout, stderr) ... or raise RepositoryError.
 
         :param cmd: git command to be executed
-        :param opts: env options to pass into Subprocess command
+        :param cwd: passed directly to subprocess
         """
-
-        if '_bare' in opts:
-            _copts = []
-            del opts['_bare']
-        else:
-            _copts = ['-c', 'core.quotepath=false', ]
-        safe_call = False
-        if '_safe' in opts:
-            # no exc on failure
-            del opts['_safe']
-            safe_call = True
-
-        assert isinstance(cmd, list), cmd
-
-        gitenv = os.environ
         # need to clean fix GIT_DIR !
-        if 'GIT_DIR' in gitenv:
-            del gitenv['GIT_DIR']
+        gitenv = dict(os.environ)
+        gitenv.pop('GIT_DIR', None)
         gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
 
-        _git_path = settings.GIT_EXECUTABLE_PATH
-        cmd = [_git_path] + _copts + cmd
+        assert isinstance(cmd, list), cmd
+        cmd = [settings.GIT_EXECUTABLE_PATH, '-c', 'core.quotepath=false'] + cmd
+        try:
+            p = subprocessio.SubprocessIOChunker(cmd, cwd=cwd, env=gitenv, shell=False)
+        except (EnvironmentError, OSError) as err:
+            # output from the failing process is in str(EnvironmentError)
+            msg = ("Couldn't run git command %s.\n"
+                   "Subprocess failed with '%s': %s\n" %
+                   (cmd, type(err).__name__, err)
+            ).strip()
+            log.error(msg)
+            raise RepositoryError(msg)
 
         try:
-            _opts = dict(
-                env=gitenv,
-                shell=False,
-            )
-            _opts.update(opts)
-            p = subprocessio.SubprocessIOChunker(cmd, **_opts)
-        except (EnvironmentError, OSError) as err:
-            tb_err = ("Couldn't run git command (%s).\n"
-                      "Original error was:%s\n" % (cmd, err))
-            log.error(tb_err)
-            if safe_call:
-                return '', err
-            else:
-                raise RepositoryError(tb_err)
-
-        try:
-            return ''.join(p.output), ''.join(p.error)
+            stdout = b''.join(p.output)
+            stderr = b''.join(p.error)
         finally:
             p.close()
+        # TODO: introduce option to make commands fail if they have any stderr output?
+        if stderr:
+            log.debug('stderr from %s:\n%s', cmd, stderr)
+        else:
+            log.debug('stderr from %s: None', cmd)
+        return stdout, stderr
 
     def run_git_command(self, cmd):
-        opts = {}
+        """
+        Runs given ``cmd`` as git command with cwd set to current repo.
+        Returns stdout as unicode str ... or raise RepositoryError.
+        """
+        cwd = None
         if os.path.isdir(self.path):
-            opts['cwd'] = self.path
-        return self._run_git_command(cmd, **opts)
+            cwd = self.path
+        stdout, _stderr = self._run_git_command(cmd, cwd=cwd)
+        return safe_str(stdout)
 
     @classmethod
     def _check_url(cls, url):
@@ -166,7 +158,6 @@
         On failures it'll raise urllib2.HTTPError, exception is also thrown
         when the return code is non 200
         """
-
         # check first if it's not an local url
         if os.path.isdir(url) or url.startswith('file:'):
             return True
@@ -178,29 +169,30 @@
             url = url[url.find('+') + 1:]
 
         handlers = []
-        url_obj = hg_url(url)
+        url_obj = mercurial.util.url(safe_bytes(url))
         test_uri, authinfo = url_obj.authinfo()
-        url_obj.passwd = '*****'
-        cleaned_uri = str(url_obj)
-
         if not test_uri.endswith('info/refs'):
             test_uri = test_uri.rstrip('/') + '/info/refs'
 
+        url_obj.passwd = b'*****'
+        cleaned_uri = str(url_obj)
+
         if authinfo:
             # create a password manager
-            passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+            passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
             passmgr.add_password(*authinfo)
 
-            handlers.extend((httpbasicauthhandler(passmgr),
-                             httpdigestauthhandler(passmgr)))
+            handlers.extend((mercurial.url.httpbasicauthhandler(passmgr),
+                             mercurial.url.httpdigestauthhandler(passmgr)))
 
-        o = urllib2.build_opener(*handlers)
+        o = urllib.request.build_opener(*handlers)
         o.addheaders = [('User-Agent', 'git/1.7.8.0')]  # fake some git
 
-        q = {"service": 'git-upload-pack'}
-        qs = '?%s' % urllib.urlencode(q)
-        cu = "%s%s" % (test_uri, qs)
-        req = urllib2.Request(cu, None, {})
+        req = urllib.request.Request(
+            "%s?%s" % (
+                test_uri,
+                urllib.parse.urlencode({"service": 'git-upload-pack'})
+            ))
 
         try:
             resp = o.open(req)
@@ -208,13 +200,13 @@
                 raise Exception('Return Code is not 200')
         except Exception as e:
             # means it cannot be cloned
-            raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
+            raise urllib.error.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
 
         # now detect if it's proper git repo
         gitdata = resp.read()
         if 'service=git-upload-pack' not in gitdata:
-            raise urllib2.URLError(
-                "url [%s] does not look like an git" % (cleaned_uri))
+            raise urllib.error.URLError(
+                "url [%s] does not look like an git" % cleaned_uri)
 
         return True
 
@@ -253,7 +245,7 @@
         rev_filter = settings.GIT_REV_FILTER
         cmd = ['rev-list', rev_filter, '--reverse', '--date-order']
         try:
-            so, se = self.run_git_command(cmd)
+            so = self.run_git_command(cmd)
         except RepositoryError:
             # Can be raised for empty repositories
             return []
@@ -261,58 +253,56 @@
 
     def _get_all_revisions2(self):
         # alternate implementation using dulwich
-        includes = [x[1][0] for x in self._parsed_refs.iteritems()
-                    if x[1][1] != 'T']
+        includes = [ascii_str(sha) for key, (sha, type_) in self._parsed_refs.items()
+                    if type_ != b'T']
         return [c.commit.id for c in self._repo.get_walker(include=includes)]
 
     def _get_revision(self, revision):
         """
-        For git backend we always return integer here. This way we ensure
-        that changeset's revision attribute would become integer.
+        Given any revision identifier, returns a 40 char string with revision hash.
         """
-
-        is_null = lambda o: len(o) == revision.count('0')
-
         if self._empty:
             raise EmptyRepositoryError("There are no changesets yet")
 
         if revision in (None, '', 'tip', 'HEAD', 'head', -1):
-            return self.revisions[-1]
+            revision = -1
 
-        is_bstr = isinstance(revision, (str, unicode))
-        if ((is_bstr and revision.isdigit() and len(revision) < 12)
-            or isinstance(revision, int) or is_null(revision)
-        ):
+        if isinstance(revision, int):
             try:
-                revision = self.revisions[int(revision)]
+                return self.revisions[revision]
             except IndexError:
-                msg = ("Revision %s does not exist for %s" % (revision, self))
+                msg = "Revision %r does not exist for %s" % (revision, self.name)
                 raise ChangesetDoesNotExistError(msg)
 
-        elif is_bstr:
-            # get by branch/tag name
-            _ref_revision = self._parsed_refs.get(revision)
-            if _ref_revision:  # and _ref_revision[1] in ['H', 'RH', 'T']:
-                return _ref_revision[0]
+        if isinstance(revision, str):
+            if revision.isdigit() and (len(revision) < 12 or len(revision) == revision.count('0')):
+                try:
+                    return self.revisions[int(revision)]
+                except IndexError:
+                    msg = "Revision %r does not exist for %s" % (revision, self)
+                    raise ChangesetDoesNotExistError(msg)
 
-            _tags_shas = self.tags.values()
+            # get by branch/tag name
+            _ref_revision = self._parsed_refs.get(safe_bytes(revision))
+            if _ref_revision:  # and _ref_revision[1] in [b'H', b'RH', b'T']:
+                return ascii_str(_ref_revision[0])
+
+            if revision in self.revisions:
+                return revision
+
             # maybe it's a tag ? we don't have them in self.revisions
-            if revision in _tags_shas:
-                return _tags_shas[_tags_shas.index(revision)]
+            if revision in self.tags.values():
+                return revision
 
-            elif not SHA_PATTERN.match(revision) or revision not in self.revisions:
-                msg = ("Revision %s does not exist for %s" % (revision, self))
+            if SHA_PATTERN.match(revision):
+                msg = "Revision %r does not exist for %s" % (revision, self.name)
                 raise ChangesetDoesNotExistError(msg)
 
-        # Ensure we return full id
-        if not SHA_PATTERN.match(str(revision)):
-            raise ChangesetDoesNotExistError("Given revision %s not recognized"
-                % revision)
-        return revision
+        raise ChangesetDoesNotExistError("Given revision %r not recognized" % revision)
 
     def get_ref_revision(self, ref_type, ref_name):
         """
-        Returns ``MercurialChangeset`` object representing repository's
+        Returns ``GitChangeset`` object representing repository's
         changeset at the given ``revision``.
         """
         return self._get_revision(ref_name)
@@ -327,20 +317,10 @@
         Returns normalized url. If schema is not given, would fall to
         filesystem (``file:///``) schema.
         """
-        url = safe_str(url)
         if url != 'default' and '://' not in url:
             url = ':///'.join(('file', url))
         return url
 
-    def get_hook_location(self):
-        """
-        returns absolute path to location where hooks are stored
-        """
-        loc = os.path.join(self.path, 'hooks')
-        if not self.bare:
-            loc = os.path.join(self.path, '.git', 'hooks')
-        return loc
-
     @LazyProperty
     def name(self):
         return os.path.basename(self.path)
@@ -367,9 +347,7 @@
 
     @LazyProperty
     def description(self):
-        undefined_description = u'unknown'
-        _desc = self._repo.get_description()
-        return safe_unicode(_desc or undefined_description)
+        return safe_str(self._repo.get_description() or b'unknown')
 
     @LazyProperty
     def contact(self):
@@ -381,8 +359,8 @@
         if not self.revisions:
             return {}
         sortkey = lambda ctx: ctx[0]
-        _branches = [(x[0], x[1][0])
-                     for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
+        _branches = [(safe_str(key), ascii_str(sha))
+                     for key, (sha, type_) in self._parsed_refs.items() if type_ == b'H']
         return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
 
     @LazyProperty
@@ -398,8 +376,8 @@
             return {}
 
         sortkey = lambda ctx: ctx[0]
-        _tags = [(x[0], x[1][0])
-                 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
+        _tags = [(safe_str(key), ascii_str(sha))
+                 for key, (sha, type_) in self._parsed_refs.items() if type_ == b'T']
         return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
 
     def tag(self, name, user, revision=None, message=None, date=None,
@@ -420,7 +398,7 @@
         changeset = self.get_changeset(revision)
         message = message or "Added tag %s for commit %s" % (name,
             changeset.raw_id)
-        self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
+        self._repo.refs[b"refs/tags/%s" % safe_bytes(name)] = changeset._commit.id
 
         self._parsed_refs = self._get_parsed_refs()
         self.tags = self._get_tags()
@@ -439,7 +417,8 @@
         """
         if name not in self.tags:
             raise TagDoesNotExistError("Tag %s does not exist" % name)
-        tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
+        # self._repo.refs is a DiskRefsContainer, and .path gives the full absolute path of '.git'
+        tagpath = os.path.join(safe_str(self._repo.refs.path), 'refs', 'tags', name)
         try:
             os.remove(tagpath)
             self._parsed_refs = self._get_parsed_refs()
@@ -459,18 +438,20 @@
         return self._get_parsed_refs()
 
     def _get_parsed_refs(self):
-        # cache the property
+        """Return refs as a dict, like:
+        { b'v0.2.0': [b'599ba911aa24d2981225f3966eb659dfae9e9f30', b'T'] }
+        """
         _repo = self._repo
         refs = _repo.get_refs()
-        keys = [('refs/heads/', 'H'),
-                ('refs/remotes/origin/', 'RH'),
-                ('refs/tags/', 'T')]
+        keys = [(b'refs/heads/', b'H'),
+                (b'refs/remotes/origin/', b'RH'),
+                (b'refs/tags/', b'T')]
         _refs = {}
-        for ref, sha in refs.iteritems():
+        for ref, sha in refs.items():
             for k, type_ in keys:
                 if ref.startswith(k):
                     _key = ref[len(k):]
-                    if type_ == 'T':
+                    if type_ == b'T':
                         obj = _repo.get_object(sha)
                         if isinstance(obj, Tag):
                             sha = _repo.get_object(sha).object[1]
@@ -483,13 +464,13 @@
         heads = {}
 
         for key, val in refs.items():
-            for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
+            for ref_key in [b'refs/heads/', b'refs/remotes/origin/']:
                 if key.startswith(ref_key):
                     n = key[len(ref_key):]
-                    if n not in ['HEAD']:
+                    if n not in [b'HEAD']:
                         heads[n] = val
 
-        return heads if reverse else dict((y, x) for x, y in heads.iteritems())
+        return heads if reverse else dict((y, x) for x, y in heads.items())
 
     def get_changeset(self, revision=None):
         """
@@ -498,9 +479,7 @@
         """
         if isinstance(revision, GitChangeset):
             return revision
-        revision = self._get_revision(revision)
-        changeset = GitChangeset(repository=self, revision=revision)
-        return changeset
+        return GitChangeset(repository=self, revision=self._get_revision(revision))
 
     def get_changesets(self, start=None, end=None, start_date=None,
            end_date=None, branch_name=None, reverse=False, max_revisions=None):
@@ -547,7 +526,7 @@
         else:
             cmd.append(settings.GIT_REV_FILTER)
 
-        revs = self.run_git_command(cmd)[0].splitlines()
+        revs = self.run_git_command(cmd).splitlines()
         start_pos = 0
         end_pos = len(revs)
         if start:
@@ -572,14 +551,15 @@
 
         revs = revs[start_pos:end_pos]
         if reverse:
-            revs = reversed(revs)
+            revs.reverse()
+
         return CollectionGenerator(self, revs)
 
     def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
                  context=3):
         """
-        Returns (git like) *diff*, as plain text. Shows changes introduced by
-        ``rev2`` since ``rev1``.
+        Returns (git like) *diff*, as plain bytes text. Shows changes
+        introduced by ``rev2`` since ``rev1``.
 
         :param rev1: Entry point from which diff is shown. Can be
           ``self.EMPTY_CHANGESET`` - in this case, patch showing all
@@ -633,14 +613,13 @@
         if path:
             cmd += ['--', path]
 
-        stdout, stderr = self.run_git_command(cmd)
-        # TODO: don't ignore stderr
+        stdout, stderr = self._run_git_command(cmd, cwd=self.path)
         # If we used 'show' command, strip first few lines (until actual diff
         # starts)
         if rev1 == self.EMPTY_CHANGESET:
-            parts = stdout.split('\ndiff ', 1)
+            parts = stdout.split(b'\ndiff ', 1)
             if len(parts) > 1:
-                stdout = 'diff ' + parts[1]
+                stdout = b'diff ' + parts[1]
         return stdout
 
     @LazyProperty
@@ -683,7 +662,7 @@
         Tries to pull changes from external location.
         """
         url = self._get_url(url)
-        so, se = self.run_git_command(['ls-remote', '-h', url])
+        so = 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')
@@ -721,7 +700,7 @@
         """
         if config_file is None:
             config_file = []
-        elif isinstance(config_file, basestring):
+        elif isinstance(config_file, str):
             config_file = [config_file]
 
         def gen_configs():
@@ -733,9 +712,10 @@
 
         for config in gen_configs():
             try:
-                return config.get(section, name)
+                value = config.get(section, name)
             except KeyError:
                 continue
+            return None if value is None else safe_str(value)
         return None
 
     def get_user_name(self, config_file=None):
--- a/kallithea/lib/vcs/backends/git/ssh.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/git/ssh.py	Thu Feb 06 01:19:23 2020 +0100
@@ -17,7 +17,6 @@
 
 from kallithea.lib.hooks import log_pull_action
 from kallithea.lib.utils import make_ui
-from kallithea.lib.utils2 import safe_str, safe_unicode
 from kallithea.lib.vcs.backends.ssh import BaseSshHandler
 
 
@@ -33,15 +32,15 @@
         >>> import shlex
 
         >>> GitSshHandler.make(shlex.split("git-upload-pack '/foo bar'")).repo_name
-        u'foo bar'
+        'foo bar'
         >>> GitSshHandler.make(shlex.split("git-upload-pack '/foo bar'")).verb
         'git-upload-pack'
         >>> GitSshHandler.make(shlex.split(" git-upload-pack /blåbærgrød ")).repo_name # might not be necessary to support no quoting ... but we can
-        u'bl\xe5b\xe6rgr\xf8d'
+        'bl\xe5b\xe6rgr\xf8d'
         >>> GitSshHandler.make(shlex.split('''git-upload-pack "/foo'bar"''')).repo_name
-        u"foo'bar"
+        "foo'bar"
         >>> GitSshHandler.make(shlex.split("git-receive-pack '/foo'")).repo_name
-        u'foo'
+        'foo'
         >>> GitSshHandler.make(shlex.split("git-receive-pack '/foo'")).verb
         'git-receive-pack'
 
@@ -56,12 +55,12 @@
             ssh_command_parts[0] in ['git-upload-pack', 'git-receive-pack'] and
             ssh_command_parts[1].startswith('/')
         ):
-            return cls(safe_unicode(ssh_command_parts[1][1:]), ssh_command_parts[0])
+            return cls(ssh_command_parts[1][1:], ssh_command_parts[0])
 
         return None
 
     def __init__(self, repo_name, verb):
-        self.repo_name = repo_name
+        BaseSshHandler.__init__(self, repo_name)
         self.verb = verb
 
     def _serve(self):
@@ -70,7 +69,7 @@
             log_pull_action(ui=make_ui(), repo=self.db_repo.scm_instance._repo)
         else: # probably verb 'git-receive-pack', action 'push'
             if not self.allow_push:
-                self.exit('Push access to %r denied' % safe_str(self.repo_name))
+                self.exit('Push access to %r denied' % self.repo_name)
             # Note: push logging is handled by Git post-receive hook
 
         # git shell is not a real shell but use shell inspired quoting *inside* the argument.
--- a/kallithea/lib/vcs/backends/git/workdir.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/git/workdir.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,5 +1,6 @@
 import re
 
+from kallithea.lib.utils2 import ascii_str, safe_str
 from kallithea.lib.vcs.backends.base import BaseWorkdir
 from kallithea.lib.vcs.exceptions import BranchDoesNotExistError, RepositoryError
 
@@ -7,9 +8,9 @@
 class GitWorkdir(BaseWorkdir):
 
     def get_branch(self):
-        headpath = self.repository._repo.refs.refpath('HEAD')
+        headpath = self.repository._repo.refs.refpath(b'HEAD')
         try:
-            content = open(headpath).read()
+            content = safe_str(open(headpath, 'rb').read())
             match = re.match(r'^ref: refs/heads/(?P<branch>.+)\n$', content)
             if match:
                 return match.groupdict()['branch']
@@ -20,7 +21,7 @@
             raise RepositoryError("Couldn't compute workdir's branch")
 
     def get_changeset(self):
-        wk_dir_id = self.repository._repo.refs.as_dict().get('HEAD')
+        wk_dir_id = ascii_str(self.repository._repo.refs.as_dict().get(b'HEAD'))
         return self.repository.get_changeset(wk_dir_id)
 
     def checkout_branch(self, branch=None):
--- a/kallithea/lib/vcs/backends/hg/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/hg/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -9,6 +9,8 @@
     :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 """
 
+from kallithea.lib.vcs.utils import hgcompat
+
 from .changeset import MercurialChangeset
 from .inmemory import MercurialInMemoryChangeset
 from .repository import MercurialRepository
@@ -19,3 +21,5 @@
     'MercurialRepository', 'MercurialChangeset',
     'MercurialInMemoryChangeset', 'MercurialWorkdir',
 ]
+
+hgcompat.monkey_do()
--- a/kallithea/lib/vcs/backends/hg/changeset.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/hg/changeset.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,41 +1,44 @@
 import os
 import posixpath
 
+import mercurial.archival
+import mercurial.node
+import mercurial.obsutil
+
 from kallithea.lib.vcs.backends.base import BaseChangeset
 from kallithea.lib.vcs.conf import settings
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
 from kallithea.lib.vcs.nodes import (
     AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode)
-from kallithea.lib.vcs.utils import date_fromtimestamp, safe_str, safe_unicode
-from kallithea.lib.vcs.utils.hgcompat import archival, hex, obsutil
+from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, date_fromtimestamp, safe_bytes, safe_str
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
 
 
 class MercurialChangeset(BaseChangeset):
     """
-    Represents state of the repository at the single revision.
+    Represents state of the repository at a revision.
     """
 
     def __init__(self, repository, revision):
         self.repository = repository
-        assert isinstance(revision, basestring), repr(revision)
-        self.raw_id = revision
-        self._ctx = repository._repo[revision]
+        assert isinstance(revision, str), repr(revision)
+        self._ctx = repository._repo[ascii_bytes(revision)]
+        self.raw_id = ascii_str(self._ctx.hex())
         self.revision = self._ctx._rev
         self.nodes = {}
 
     @LazyProperty
     def tags(self):
-        return map(safe_unicode, self._ctx.tags())
+        return [safe_str(tag) for tag in self._ctx.tags()]
 
     @LazyProperty
     def branch(self):
-        return safe_unicode(self._ctx.branch())
+        return safe_str(self._ctx.branch())
 
     @LazyProperty
     def branches(self):
-        return [safe_unicode(self._ctx.branch())]
+        return [safe_str(self._ctx.branch())]
 
     @LazyProperty
     def closesbranch(self):
@@ -47,17 +50,11 @@
 
     @LazyProperty
     def bumped(self):
-        try:
-            return self._ctx.phasedivergent()
-        except AttributeError: # renamed in Mercurial 4.6 (9fa874fb34e1)
-            return self._ctx.bumped()
+        return self._ctx.phasedivergent()
 
     @LazyProperty
     def divergent(self):
-        try:
-            return self._ctx.contentdivergent()
-        except AttributeError: # renamed in Mercurial 4.6 (8b2d7684407b)
-            return self._ctx.divergent()
+        return self._ctx.contentdivergent()
 
     @LazyProperty
     def extinct(self):
@@ -65,10 +62,7 @@
 
     @LazyProperty
     def unstable(self):
-        try:
-            return self._ctx.orphan()
-        except AttributeError: # renamed in Mercurial 4.6 (03039ff3082b)
-            return self._ctx.unstable()
+        return self._ctx.orphan()
 
     @LazyProperty
     def phase(self):
@@ -81,33 +75,33 @@
 
     @LazyProperty
     def successors(self):
-        successors = obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
+        successors = mercurial.obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
         if successors:
             # flatten the list here handles both divergent (len > 1)
             # and the usual case (len = 1)
-            successors = [hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
+            successors = [mercurial.node.hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
 
         return successors
 
     @LazyProperty
     def predecessors(self):
-        return [hex(n)[:12] for n in obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
+        return [mercurial.node.hex(n)[:12] for n in mercurial.obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
 
     @LazyProperty
     def bookmarks(self):
-        return map(safe_unicode, self._ctx.bookmarks())
+        return [safe_str(bookmark) for bookmark in self._ctx.bookmarks()]
 
     @LazyProperty
     def message(self):
-        return safe_unicode(self._ctx.description())
+        return safe_str(self._ctx.description())
 
     @LazyProperty
     def committer(self):
-        return safe_unicode(self.author)
+        return safe_str(self.author)
 
     @LazyProperty
     def author(self):
-        return safe_unicode(self._ctx.user())
+        return safe_str(self._ctx.user())
 
     @LazyProperty
     def date(self):
@@ -127,7 +121,7 @@
 
     @LazyProperty
     def _file_paths(self):
-        return list(self._ctx)
+        return list(safe_str(f) for f in self._ctx)
 
     @LazyProperty
     def _dir_paths(self):
@@ -140,12 +134,6 @@
         return self._dir_paths + self._file_paths
 
     @LazyProperty
-    def id(self):
-        if self.last:
-            return u'tip'
-        return self.short_id
-
-    @LazyProperty
     def short_id(self):
         return self.raw_id[:12]
 
@@ -202,8 +190,8 @@
                 return cs
 
     def diff(self):
-        # Only used for feed diffstat
-        return ''.join(self._ctx.diff())
+        # Only used to feed diffstat
+        return b''.join(self._ctx.diff())
 
     def _fix_path(self, path):
         """
@@ -214,7 +202,7 @@
         if path.endswith('/'):
             path = path.rstrip('/')
 
-        return safe_str(path)
+        return path
 
     def _get_kind(self, path):
         path = self._fix_path(path)
@@ -231,7 +219,7 @@
         if self._get_kind(path) != NodeKind.FILE:
             raise ChangesetError("File does not exist for revision %s at "
                 " '%s'" % (self.raw_id, path))
-        return self._ctx.filectx(path)
+        return self._ctx.filectx(safe_bytes(path))
 
     def _extract_submodules(self):
         """
@@ -245,10 +233,10 @@
         Returns stat mode of the file at the given ``path``.
         """
         fctx = self._get_filectx(path)
-        if 'x' in fctx.flags():
-            return 0100755
+        if b'x' in fctx.flags():
+            return 0o100755
         else:
-            return 0100644
+            return 0o100644
 
     def get_file_content(self, path):
         """
@@ -280,7 +268,7 @@
         cnt = 0
         for cs in reversed([x for x in fctx.filelog()]):
             cnt += 1
-            hist.append(hex(fctx.filectx(cs).node()))
+            hist.append(mercurial.node.hex(fctx.filectx(cs).node()))
             if limit is not None and cnt == limit:
                 break
 
@@ -292,13 +280,10 @@
             lineno, sha, changeset lazy loader and line
         """
         annotations = self._get_filectx(path).annotate()
-        try:
-            annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
-        except AttributeError: # annotateline was introduced in Mercurial 4.6 (b33b91ca2ec2)
-            annotation_lines = [(aline.fctx, l) for aline, l in annotations]
-        for i, (fctx, l) in enumerate(annotation_lines):
-            sha = fctx.hex()
-            yield (i + 1, sha, lambda sha=sha, l=l: self.repository.get_changeset(sha), l)
+        annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
+        for i, (fctx, line) in enumerate(annotation_lines):
+            sha = ascii_str(fctx.hex())
+            yield (i + 1, sha, lambda sha=sha: self.repository.get_changeset(sha), line)
 
     def fill_archive(self, stream=None, kind='tgz', prefix=None,
                      subrepos=False):
@@ -316,11 +301,10 @@
         :raise ImproperArchiveTypeError: If given kind is wrong.
         :raise VcsError: If given stream is None
         """
-
-        allowed_kinds = settings.ARCHIVE_SPECS.keys()
+        allowed_kinds = settings.ARCHIVE_SPECS
         if kind not in allowed_kinds:
             raise ImproperArchiveTypeError('Archive kind not supported use one'
-                'of %s' % allowed_kinds)
+                'of %s' % ' '.join(allowed_kinds))
 
         if stream is None:
             raise VCSError('You need to pass in a valid stream for filling'
@@ -333,8 +317,8 @@
         elif prefix.strip() == '':
             raise VCSError("Prefix cannot be empty")
 
-        archival.archive(self.repository._repo, stream, self.raw_id,
-                         kind, prefix=prefix, subrepos=subrepos)
+        mercurial.archival.archive(self.repository._repo, stream, ascii_bytes(self.raw_id),
+                         safe_bytes(kind), prefix=safe_bytes(prefix), subrepos=subrepos)
 
     def get_nodes(self, path):
         """
@@ -356,18 +340,16 @@
             if os.path.dirname(d) == path]
 
         als = self.repository.alias
-        for k, vals in self._extract_submodules().iteritems():
+        for k, vals in self._extract_submodules().items():
             #vals = url,rev,type
             loc = vals[0]
             cs = vals[1]
             dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
                                           alias=als))
         nodes = dirnodes + filenodes
-        # cache nodes
         for node in nodes:
             self.nodes[node.path] = node
         nodes.sort()
-
         return nodes
 
     def get_node(self, path):
@@ -375,9 +357,7 @@
         Returns ``Node`` object from the given ``path``. If there is no node at
         the given ``path``, ``ChangesetError`` would be raised.
         """
-
         path = self._fix_path(path)
-
         if path not in self.nodes:
             if path in self._file_paths:
                 node = FileNode(path, changeset=self)
@@ -406,21 +386,21 @@
         """
         Returns list of added ``FileNode`` objects.
         """
-        return AddedFileNodesGenerator([n for n in self.status[1]], self)
+        return AddedFileNodesGenerator([safe_str(n) for n in self.status.added], self)
 
     @property
     def changed(self):
         """
         Returns list of modified ``FileNode`` objects.
         """
-        return ChangedFileNodesGenerator([n for n in self.status[0]], self)
+        return ChangedFileNodesGenerator([safe_str(n) for n in self.status.modified], self)
 
     @property
     def removed(self):
         """
         Returns list of removed ``FileNode`` objects.
         """
-        return RemovedFileNodesGenerator([n for n in self.status[2]], self)
+        return RemovedFileNodesGenerator([safe_str(n) for n in self.status.removed], self)
 
     @LazyProperty
     def extra(self):
--- a/kallithea/lib/vcs/backends/hg/inmemory.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/hg/inmemory.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,14 +1,17 @@
 import datetime
 
+import mercurial.context
+import mercurial.node
+
 from kallithea.lib.vcs.backends.base import BaseInMemoryChangeset
 from kallithea.lib.vcs.exceptions import RepositoryError
-from kallithea.lib.vcs.utils.hgcompat import hex, memctx, memfilectx, tolocal
+from kallithea.lib.vcs.utils import ascii_str, safe_bytes, safe_str
 
 
 class MercurialInMemoryChangeset(BaseInMemoryChangeset):
 
     def commit(self, message, author, parents=None, branch=None, date=None,
-            **kwargs):
+               **kwargs):
         """
         Performs in-memory commit (doesn't check workdir in any way) and
         returns newly created ``Changeset``. Updates repository's
@@ -27,21 +30,22 @@
         """
         self.check_integrity(parents)
 
+        if not isinstance(message, str):
+            raise RepositoryError('message must be a str - got %r' % type(message))
+        if not isinstance(author, str):
+            raise RepositoryError('author must be a str - got %r' % type(author))
+
         from .repository import MercurialRepository
-        if not isinstance(message, unicode) or not isinstance(author, unicode):
-            raise RepositoryError('Given message and author needs to be '
-                                  'an <unicode> instance got %r & %r instead'
-                                  % (type(message), type(author)))
-
         if branch is None:
             branch = MercurialRepository.DEFAULT_BRANCH_NAME
-        kwargs['branch'] = branch
+        kwargs[b'branch'] = safe_bytes(branch)
 
-        def filectxfn(_repo, memctx, path):
+        def filectxfn(_repo, memctx, bytes_path):
             """
-            Marks given path as added/changed/removed in a given _repo. This is
-            for internal mercurial commit function.
+            Callback from Mercurial, returning ctx to commit for the given
+            path.
             """
+            path = safe_str(bytes_path)
 
             # check if this path is removed
             if path in (node.path for node in self.removed):
@@ -50,9 +54,8 @@
             # check if this path is added
             for node in self.added:
                 if node.path == path:
-                    return memfilectx(_repo, memctx, path=node.path,
-                        data=(node.content.encode('utf-8')
-                              if not node.is_binary else node.content),
+                    return mercurial.context.memfilectx(_repo, memctx, path=bytes_path,
+                        data=node.content,
                         islink=False,
                         isexec=node.is_executable,
                         copysource=False)
@@ -60,14 +63,13 @@
             # or changed
             for node in self.changed:
                 if node.path == path:
-                    return memfilectx(_repo, memctx, path=node.path,
-                        data=(node.content.encode('utf-8')
-                              if not node.is_binary else node.content),
+                    return mercurial.context.memfilectx(_repo, memctx, path=bytes_path,
+                        data=node.content,
                         islink=False,
                         isexec=node.is_executable,
                         copysource=False)
 
-            raise RepositoryError("Given path haven't been marked as added,"
+            raise RepositoryError("Given path haven't been marked as added, "
                                   "changed or removed (%s)" % path)
 
         parents = [None, None]
@@ -76,22 +78,21 @@
                 parents[i] = parent._ctx.node()
 
         if date and isinstance(date, datetime.datetime):
-            date = date.strftime('%a, %d %b %Y %H:%M:%S')
+            date = safe_bytes(date.strftime('%a, %d %b %Y %H:%M:%S'))
 
-        commit_ctx = memctx(repo=self.repository._repo,
+        commit_ctx = mercurial.context.memctx(
+            repo=self.repository._repo,
             parents=parents,
-            text='',
-            files=self.get_paths(),
+            text=b'',
+            files=[safe_bytes(x) for x in self.get_paths()],
             filectxfn=filectxfn,
-            user=author,
+            user=safe_bytes(author),
             date=date,
             extra=kwargs)
 
-        loc = lambda u: tolocal(u.encode('utf-8'))
-
         # injecting given _repo params
-        commit_ctx._text = loc(message)
-        commit_ctx._user = loc(author)
+        commit_ctx._text = safe_bytes(message)
+        commit_ctx._user = safe_bytes(author)
         commit_ctx._date = date
 
         # TODO: Catch exceptions!
@@ -100,9 +101,8 @@
         self._commit_ctx = commit_ctx  # For reference
         # Update vcs repository object & recreate mercurial _repo
         # new_ctx = self.repository._repo[node]
-        # new_tip = self.repository.get_changeset(new_ctx.hex())
-        new_id = hex(n)
-        self.repository.revisions.append(new_id)
+        # new_tip = ascii_str(self.repository.get_changeset(new_ctx.hex()))
+        self.repository.revisions.append(ascii_str(mercurial.node.hex(n)))
         self._repo = self.repository._get_repo(create=False)
         self.repository.branches = self.repository._get_branches()
         tip = self.repository.get_changeset()
--- a/kallithea/lib/vcs/backends/hg/repository.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Thu Feb 06 01:19:23 2020 +0100
@@ -13,16 +13,33 @@
 import logging
 import os
 import time
-import urllib
-import urllib2
+import urllib.error
+import urllib.parse
+import urllib.request
 from collections import OrderedDict
 
+import mercurial.commands
+import mercurial.error
+import mercurial.exchange
+import mercurial.hg
+import mercurial.hgweb
+import mercurial.httppeer
+import mercurial.localrepo
+import mercurial.match
+import mercurial.mdiff
+import mercurial.node
+import mercurial.patch
+import mercurial.scmutil
+import mercurial.sshpeer
+import mercurial.tags
+import mercurial.ui
+import mercurial.url
+import mercurial.util
+
 from kallithea.lib.vcs.backends.base import BaseRepository, CollectionGenerator
 from kallithea.lib.vcs.exceptions import (
     BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError, TagAlreadyExistError, TagDoesNotExistError, VCSError)
-from kallithea.lib.vcs.utils import author_email, author_name, date_fromtimestamp, makedate, safe_str, safe_unicode
-from kallithea.lib.vcs.utils.hgcompat import (
-    Abort, RepoError, RepoLookupError, clone, diffopts, get_contact, hex, hg_url, httpbasicauthhandler, httpdigestauthhandler, httppeer, localrepo, match_exact, nullid, patch, peer, scmutil, sshpeer, tag, ui)
+from kallithea.lib.vcs.utils import ascii_str, author_email, author_name, date_fromtimestamp, makedate, safe_bytes, safe_str
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.lib.vcs.utils.paths import abspath
 
@@ -60,9 +77,8 @@
             raise VCSError('Mercurial backend requires repository path to '
                            'be instance of <str> got %s instead' %
                            type(repo_path))
-
         self.path = abspath(repo_path)
-        self.baseui = baseui or ui.ui()
+        self.baseui = baseui or mercurial.ui.ui()
         # We've set path and ui, now we can set _repo itself
         self._repo = self._get_repo(create, src_url, update_after_clone)
 
@@ -115,14 +131,13 @@
             return {}
 
         bt = OrderedDict()
-        for bn, _heads, tip, isclosed in sorted(self._repo.branchmap().iterbranches()):
+        for bn, _heads, node, isclosed in sorted(self._repo.branchmap().iterbranches()):
             if isclosed:
                 if closed:
-                    bt[safe_unicode(bn)] = hex(tip)
+                    bt[safe_str(bn)] = ascii_str(mercurial.node.hex(node))
             else:
                 if normal:
-                    bt[safe_unicode(bn)] = hex(tip)
-
+                    bt[safe_str(bn)] = ascii_str(mercurial.node.hex(node))
         return bt
 
     @LazyProperty
@@ -136,11 +151,11 @@
         if self._empty:
             return {}
 
-        sortkey = lambda ctx: ctx[0]  # sort by name
-        _tags = [(safe_unicode(n), hex(h),) for n, h in
-                 self._repo.tags().items()]
-
-        return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
+        return OrderedDict(sorted(
+            ((safe_str(n), ascii_str(mercurial.node.hex(h))) for n, h in self._repo.tags().items()),
+            reverse=True,
+            key=lambda x: x[0],  # sort by name
+        ))
 
     def tag(self, name, user, revision=None, message=None, date=None,
             **kwargs):
@@ -165,12 +180,12 @@
                 changeset.short_id)
 
         if date is None:
-            date = datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')
+            date = safe_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S'))
 
         try:
-            tag(self._repo, name, changeset._ctx.node(), message, local, user, date)
-        except Abort as e:
-            raise RepositoryError(e.message)
+            mercurial.tags.tag(self._repo, safe_bytes(name), changeset._ctx.node(), safe_bytes(message), local, safe_bytes(user), date)
+        except mercurial.error.Abort as e:
+            raise RepositoryError(e.args[0])
 
         # Reinitialize tags
         self.tags = self._get_tags()
@@ -194,14 +209,14 @@
         if message is None:
             message = "Removed tag %s" % name
         if date is None:
-            date = datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')
+            date = safe_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S'))
         local = False
 
         try:
-            tag(self._repo, name, nullid, message, local, user, date)
+            mercurial.tags.tag(self._repo, safe_bytes(name), mercurial.commands.nullid, safe_bytes(message), local, safe_bytes(user), date)
             self.tags = self._get_tags()
-        except Abort as e:
-            raise RepositoryError(e.message)
+        except mercurial.error.Abort as e:
+            raise RepositoryError(e.args[0])
 
     @LazyProperty
     def bookmarks(self):
@@ -214,14 +229,14 @@
         if self._empty:
             return {}
 
-        sortkey = lambda ctx: ctx[0]  # sort by name
-        _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
-                 self._repo._bookmarks.items()]
-        return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
+        return OrderedDict(sorted(
+            ((safe_str(n), ascii_str(h)) for n, h in self._repo._bookmarks.items()),
+            reverse=True,
+            key=lambda x: x[0],  # sort by name
+        ))
 
     def _get_all_revisions(self):
-
-        return [self._repo[x].hex() for x in self._repo.filtered('visible').changelog.revs()]
+        return [ascii_str(self._repo[x].hex()) for x in self._repo.filtered(b'visible').changelog.revs()]
 
     def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
                   context=3):
@@ -257,12 +272,12 @@
             self.get_changeset(rev1)
         self.get_changeset(rev2)
         if path:
-            file_filter = match_exact(path)
+            file_filter = mercurial.match.exact(path)
         else:
             file_filter = None
 
-        return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
-                          opts=diffopts(git=True,
+        return b''.join(mercurial.patch.diff(self._repo, rev1, rev2, match=file_filter,
+                          opts=mercurial.mdiff.diffopts(git=True,
                                         showfunc=True,
                                         ignorews=ignore_whitespace,
                                         context=context)))
@@ -279,42 +294,45 @@
         when the return code is non 200
         """
         # check first if it's not an local url
-        if os.path.isdir(url) or url.startswith('file:'):
+        if os.path.isdir(url) or url.startswith(b'file:'):
             return True
 
-        if url.startswith('ssh:'):
+        if url.startswith(b'ssh:'):
             # in case of invalid uri or authentication issues, sshpeer will
             # throw an exception.
-            sshpeer.instance(repoui or ui.ui(), url, False).lookup('tip')
+            mercurial.sshpeer.instance(repoui or mercurial.ui.ui(), url, False).lookup(b'tip')
             return True
 
         url_prefix = None
-        if '+' in url[:url.find('://')]:
-            url_prefix, url = url.split('+', 1)
+        if b'+' in url[:url.find(b'://')]:
+            url_prefix, url = url.split(b'+', 1)
 
         handlers = []
-        url_obj = hg_url(url)
+        url_obj = mercurial.util.url(url)
         test_uri, authinfo = url_obj.authinfo()
-        url_obj.passwd = '*****'
+        url_obj.passwd = b'*****'
         cleaned_uri = str(url_obj)
 
         if authinfo:
             # create a password manager
-            passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+            passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
             passmgr.add_password(*authinfo)
 
-            handlers.extend((httpbasicauthhandler(passmgr),
-                             httpdigestauthhandler(passmgr)))
+            handlers.extend((mercurial.url.httpbasicauthhandler(passmgr),
+                             mercurial.url.httpdigestauthhandler(passmgr)))
 
-        o = urllib2.build_opener(*handlers)
+        o = urllib.request.build_opener(*handlers)
         o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
                         ('Accept', 'application/mercurial-0.1')]
 
-        q = {"cmd": 'between'}
-        q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
-        qs = '?%s' % urllib.urlencode(q)
-        cu = "%s%s" % (test_uri, qs)
-        req = urllib2.Request(cu, None, {})
+        req = urllib.request.Request(
+            "%s?%s" % (
+                test_uri,
+                urllib.parse.urlencode({
+                    'cmd': 'between',
+                    'pairs': "%s-%s" % ('0' * 40, '0' * 40),
+                })
+            ))
 
         try:
             resp = o.open(req)
@@ -322,14 +340,14 @@
                 raise Exception('Return Code is not 200')
         except Exception as e:
             # means it cannot be cloned
-            raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
+            raise urllib.error.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
 
         if not url_prefix: # skip svn+http://... (and git+... too)
             # now check if it's a proper hg repo
             try:
-                httppeer.instance(repoui or ui.ui(), url, False).lookup('tip')
+                mercurial.httppeer.instance(repoui or mercurial.ui.ui(), url, False).lookup(b'tip')
             except Exception as e:
-                raise urllib2.URLError(
+                raise urllib.error.URLError(
                     "url [%s] does not look like an hg repo org_exc: %s"
                     % (cleaned_uri, e))
 
@@ -345,26 +363,25 @@
         location at given clone_point. Additionally it'll make update to
         working copy accordingly to ``update_after_clone`` flag
         """
-
         try:
             if src_url:
-                url = safe_str(self._get_url(src_url))
+                url = safe_bytes(self._get_url(src_url))
                 opts = {}
                 if not update_after_clone:
                     opts.update({'noupdate': True})
                 MercurialRepository._check_url(url, self.baseui)
-                clone(self.baseui, url, self.path, **opts)
+                mercurial.commands.clone(self.baseui, url, safe_bytes(self.path), **opts)
 
                 # Don't try to create if we've already cloned repo
                 create = False
-            return localrepo.instance(self.baseui, self.path, create=create)
-        except (Abort, RepoError) as err:
+            return mercurial.localrepo.instance(self.baseui, safe_bytes(self.path), create=create)
+        except (mercurial.error.Abort, mercurial.error.RepoError) as err:
             if create:
                 msg = "Cannot create repository at %s. Original error was %s" \
-                    % (self.path, err)
+                    % (self.name, err)
             else:
                 msg = "Not valid repository at %s. Original error was %s" \
-                    % (self.path, err)
+                    % (self.name, err)
             raise RepositoryError(msg)
 
     @LazyProperty
@@ -373,15 +390,13 @@
 
     @LazyProperty
     def description(self):
-        undefined_description = u'unknown'
-        _desc = self._repo.ui.config('web', 'description', None, untrusted=True)
-        return safe_unicode(_desc or undefined_description)
+        _desc = self._repo.ui.config(b'web', b'description', None, untrusted=True)
+        return safe_str(_desc or b'unknown')
 
     @LazyProperty
     def contact(self):
-        undefined_contact = u'Unknown'
-        return safe_unicode(get_contact(self._repo.ui.config)
-                            or undefined_contact)
+        return safe_str(mercurial.hgweb.common.get_contact(self._repo.ui.config)
+                            or b'Unknown')
 
     @LazyProperty
     def last_change(self):
@@ -404,39 +419,33 @@
 
     def _get_revision(self, revision):
         """
-        Gets an ID revision given as str. This will always return a full
-        40 char revision number
+        Given any revision identifier, returns a 40 char string with revision hash.
 
         :param revision: str or int or None
         """
-        if isinstance(revision, unicode):
-            revision = safe_str(revision)
-
         if self._empty:
             raise EmptyRepositoryError("There are no changesets yet")
 
         if revision in [-1, None]:
-            revision = 'tip'
+            revision = b'tip'
+        elif isinstance(revision, str):
+            revision = safe_bytes(revision)
 
         try:
             if isinstance(revision, int):
-                return self._repo[revision].hex()
-            try:
-                return scmutil.revsymbol(self._repo, revision).hex()
-            except AttributeError: # revsymbol was introduced in Mercurial 4.6
-                return self._repo[revision].hex()
-        except (IndexError, ValueError, RepoLookupError, TypeError):
-            msg = ("Revision %s does not exist for %s" % (revision, self))
+                return ascii_str(self._repo[revision].hex())
+            return ascii_str(mercurial.scmutil.revsymbol(self._repo, revision).hex())
+        except (IndexError, ValueError, mercurial.error.RepoLookupError, TypeError):
+            msg = "Revision %r does not exist for %s" % (safe_str(revision), self.name)
             raise ChangesetDoesNotExistError(msg)
         except (LookupError, ):
-            msg = ("Ambiguous identifier `%s` for %s" % (revision, self))
+            msg = "Ambiguous identifier `%s` for %s" % (safe_str(revision), self.name)
             raise ChangesetDoesNotExistError(msg)
 
     def get_ref_revision(self, ref_type, ref_name):
         """
         Returns revision number for the given reference.
         """
-        ref_name = safe_str(ref_name)
         if ref_type == 'rev' and not ref_name.strip('0'):
             return self.EMPTY_CHANGESET
         # lookup up the exact node id
@@ -451,17 +460,13 @@
         try:
             revs = self._repo.revs(rev_spec, ref_name, ref_name)
         except LookupError:
-            msg = ("Ambiguous identifier %s:%s for %s" % (ref_type, ref_name, self.name))
+            msg = "Ambiguous identifier %s:%s for %s" % (ref_type, ref_name, self.name)
             raise ChangesetDoesNotExistError(msg)
-        except RepoLookupError:
-            msg = ("Revision %s:%s does not exist for %s" % (ref_type, ref_name, self.name))
+        except mercurial.error.RepoLookupError:
+            msg = "Revision %s:%s does not exist for %s" % (ref_type, ref_name, self.name)
             raise ChangesetDoesNotExistError(msg)
         if revs:
-            try:
-                revision = revs.last()
-            except AttributeError:
-                # removed in hg 3.2
-                revision = revs[-1]
+            revision = revs.last()
         else:
             # TODO: just report 'not found'?
             revision = ref_name
@@ -469,39 +474,29 @@
         return self._get_revision(revision)
 
     def _get_archives(self, archive_name='tip'):
-        allowed = self.baseui.configlist("web", "allow_archive",
+        allowed = self.baseui.configlist(b"web", b"allow_archive",
                                          untrusted=True)
-        for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
-            if i[0] in allowed or self._repo.ui.configbool("web",
-                                                           "allow" + i[0],
+        for name, ext in [(b'zip', '.zip'), (b'gz', '.tar.gz'), (b'bz2', '.tar.bz2')]:
+            if name in allowed or self._repo.ui.configbool(b"web",
+                                                           b"allow" + name,
                                                            untrusted=True):
-                yield {"type": i[0], "extension": i[1], "node": archive_name}
+                yield {"type": safe_str(name), "extension": ext, "node": archive_name}
 
     def _get_url(self, url):
         """
-        Returns normalized url. If schema is not given, would fall
-        to filesystem
-        (``file:///``) schema.
+        Returns normalized url. If schema is not given, fall back to
+        filesystem (``file:///``) schema.
         """
-        url = safe_str(url)
         if url != 'default' and '://' not in url:
-            url = "file:" + urllib.pathname2url(url)
+            url = "file:" + urllib.request.pathname2url(url)
         return url
 
-    def get_hook_location(self):
-        """
-        returns absolute path to location where hooks are stored
-        """
-        return os.path.join(self.path, '.hg', '.hgrc')
-
     def get_changeset(self, revision=None):
         """
         Returns ``MercurialChangeset`` object representing repository's
         changeset at the given ``revision``.
         """
-        revision = self._get_revision(revision)
-        changeset = MercurialChangeset(repository=self, revision=revision)
-        return changeset
+        return MercurialChangeset(repository=self, revision=self._get_revision(revision))
 
     def get_changesets(self, start=None, end=None, start_date=None,
                        end_date=None, branch_name=None, reverse=False, max_revisions=None):
@@ -517,35 +512,35 @@
         :param reversed: return changesets in reversed order
         """
         start_raw_id = self._get_revision(start)
-        start_pos = self.revisions.index(start_raw_id) if start else None
+        start_pos = None if start is None else self.revisions.index(start_raw_id)
         end_raw_id = self._get_revision(end)
-        end_pos = self.revisions.index(end_raw_id) if end else None
+        end_pos = None if end is None else self.revisions.index(end_raw_id)
 
-        if None not in [start, end] and start_pos > end_pos:
+        if start_pos is not None and end_pos is not None and start_pos > end_pos:
             raise RepositoryError("Start revision '%s' cannot be "
                                   "after end revision '%s'" % (start, end))
 
-        if branch_name and branch_name not in self.allbranches.keys():
-            msg = ("Branch %s not found in %s" % (branch_name, self))
+        if branch_name and branch_name not in self.allbranches:
+            msg = "Branch %r not found in %s" % (branch_name, self.name)
             raise BranchDoesNotExistError(msg)
         if end_pos is not None:
             end_pos += 1
         # filter branches
         filter_ = []
         if branch_name:
-            filter_.append('branch("%s")' % safe_str(branch_name))
+            filter_.append(b'branch("%s")' % safe_bytes(branch_name))
         if start_date:
-            filter_.append('date(">%s")' % start_date)
+            filter_.append(b'date(">%s")' % safe_bytes(str(start_date)))
         if end_date:
-            filter_.append('date("<%s")' % end_date)
+            filter_.append(b'date("<%s")' % safe_bytes(str(end_date)))
         if filter_ or max_revisions:
             if filter_:
-                revspec = ' and '.join(filter_)
+                revspec = b' and '.join(filter_)
             else:
-                revspec = 'all()'
+                revspec = b'all()'
             if max_revisions:
-                revspec = 'limit(%s, %s)' % (revspec, max_revisions)
-            revisions = scmutil.revrange(self._repo, [revspec])
+                revspec = b'limit(%s, %d)' % (revspec, max_revisions)
+            revisions = mercurial.scmutil.revrange(self._repo, [revspec])
         else:
             revisions = self.revisions
 
@@ -553,7 +548,7 @@
         # would be to get rid of this function entirely and use revsets
         revs = list(revisions)[start_pos:end_pos]
         if reverse:
-            revs = reversed(revs)
+            revs.reverse()
 
         return CollectionGenerator(self, revs)
 
@@ -561,15 +556,10 @@
         """
         Tries to pull changes from external location.
         """
-        url = self._get_url(url)
-        other = peer(self._repo, {}, url)
+        other = mercurial.hg.peer(self._repo, {}, safe_bytes(self._get_url(url)))
         try:
-            # hg 3.2 moved push / pull to exchange module
-            from mercurial import exchange
-            exchange.pull(self._repo, other, heads=None, force=None)
-        except ImportError:
-            self._repo.pull(other, heads=None, force=None)
-        except Abort as err:
+            mercurial.exchange.pull(self._repo, other, heads=None, force=None)
+        except mercurial.error.Abort as err:
             # Propagate error but with vcs's type
             raise RepositoryError(str(err))
 
@@ -591,15 +581,16 @@
         """
         if config_file is None:
             config_file = []
-        elif isinstance(config_file, basestring):
+        elif isinstance(config_file, str):
             config_file = [config_file]
 
         config = self._repo.ui
         if config_file:
-            config = ui.ui()
+            config = mercurial.ui.ui()
             for path in config_file:
-                config.readconfig(path)
-        return config.config(section, name)
+                config.readconfig(safe_bytes(path))
+        value = config.config(safe_bytes(section), safe_bytes(name))
+        return value if value is None else safe_str(value)
 
     def get_user_name(self, config_file=None):
         """
--- a/kallithea/lib/vcs/backends/hg/ssh.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/hg/ssh.py	Thu Feb 06 01:19:23 2020 +0100
@@ -14,18 +14,12 @@
 
 import logging
 
-from mercurial import hg
+import mercurial.hg
+import mercurial.wireprotoserver
 
 from kallithea.lib.utils import make_ui
-from kallithea.lib.utils2 import safe_str, safe_unicode
 from kallithea.lib.vcs.backends.ssh import BaseSshHandler
-
-
-try:
-    from mercurial.wireprotoserver import sshserver
-except ImportError:
-    from mercurial.sshserver import sshserver # moved in Mercurial 4.6 (1bf5263fe5cc)
-
+from kallithea.lib.vcs.utils import safe_bytes
 
 
 log = logging.getLogger(__name__)
@@ -40,11 +34,11 @@
         >>> import shlex
 
         >>> MercurialSshHandler.make(shlex.split('hg -R "foo bar" serve --stdio')).repo_name
-        u'foo bar'
+        'foo bar'
         >>> MercurialSshHandler.make(shlex.split(' hg -R blåbærgrød serve --stdio ')).repo_name
-        u'bl\xe5b\xe6rgr\xf8d'
+        'bl\xe5b\xe6rgr\xf8d'
         >>> MercurialSshHandler.make(shlex.split('''hg -R 'foo"bar' serve --stdio''')).repo_name
-        u'foo"bar'
+        'foo"bar'
 
         >>> MercurialSshHandler.make(shlex.split('/bin/hg -R "foo" serve --stdio'))
         >>> MercurialSshHandler.make(shlex.split('''hg -R "foo"bar" serve --stdio''')) # ssh-serve will report: Error parsing SSH command "...": invalid syntax
@@ -53,20 +47,17 @@
         >>> MercurialSshHandler.make(shlex.split('git-upload-pack "/foo"')) # not handled here
         """
         if ssh_command_parts[:2] == ['hg', '-R'] and ssh_command_parts[3:] == ['serve', '--stdio']:
-            return cls(safe_unicode(ssh_command_parts[2]))
+            return cls(ssh_command_parts[2])
 
         return None
 
-    def __init__(self, repo_name):
-        self.repo_name = repo_name
-
     def _serve(self):
         # Note: we want a repo with config based on .hg/hgrc and can thus not use self.db_repo.scm_instance._repo.ui
         baseui = make_ui(repo_path=self.db_repo.repo_full_path)
         if not self.allow_push:
-            baseui.setconfig('hooks', 'pretxnopen._ssh_reject', 'python:kallithea.lib.hooks.rejectpush')
-            baseui.setconfig('hooks', 'prepushkey._ssh_reject', 'python:kallithea.lib.hooks.rejectpush')
+            baseui.setconfig(b'hooks', b'pretxnopen._ssh_reject', b'python:kallithea.lib.hooks.rejectpush')
+            baseui.setconfig(b'hooks', b'prepushkey._ssh_reject', b'python:kallithea.lib.hooks.rejectpush')
 
-        repo = hg.repository(baseui, safe_str(self.db_repo.repo_full_path))
+        repo = mercurial.hg.repository(baseui, safe_bytes(self.db_repo.repo_full_path))
         log.debug("Starting Mercurial sshserver for %s", self.db_repo.repo_full_path)
-        sshserver(baseui, repo).serve_forever()
+        mercurial.wireprotoserver.sshserver(baseui, repo).serve_forever()
--- a/kallithea/lib/vcs/backends/hg/workdir.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/hg/workdir.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,15 +1,17 @@
+import mercurial.merge
+
 from kallithea.lib.vcs.backends.base import BaseWorkdir
 from kallithea.lib.vcs.exceptions import BranchDoesNotExistError
-from kallithea.lib.vcs.utils.hgcompat import hg_merge
+from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, safe_str
 
 
 class MercurialWorkdir(BaseWorkdir):
 
     def get_branch(self):
-        return self.repository._repo.dirstate.branch()
+        return safe_str(self.repository._repo.dirstate.branch())
 
     def get_changeset(self):
-        wk_dir_id = self.repository._repo[None].parents()[0].hex()
+        wk_dir_id = ascii_str(self.repository._repo[None].parents()[0].hex())
         return self.repository.get_changeset(wk_dir_id)
 
     def checkout_branch(self, branch=None):
@@ -19,4 +21,4 @@
             raise BranchDoesNotExistError
 
         raw_id = self.repository.branches[branch]
-        hg_merge.update(self.repository._repo, raw_id, False, False, None)
+        mercurial.merge.update(self.repository._repo, ascii_bytes(raw_id), False, False, None)
--- a/kallithea/lib/vcs/backends/ssh.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/backends/ssh.py	Thu Feb 06 01:19:23 2020 +0100
@@ -24,7 +24,7 @@
 import sys
 
 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
-from kallithea.lib.utils2 import safe_str, set_hook_environment
+from kallithea.lib.utils2 import set_hook_environment
 from kallithea.model.db import Repository, User, UserSshKeys
 from kallithea.model.meta import Session
 
@@ -55,6 +55,9 @@
         """
         raise NotImplementedError
 
+    def __init__(self, repo_name):
+        self.repo_name = repo_name.rstrip('/')
+
     def serve(self, user_id, key_id, client_ip):
         """Verify basic sanity of the repository, and that the user is
         valid and has access - then serve the native VCS protocol for
@@ -79,7 +82,7 @@
         elif HasPermissionAnyMiddleware('repository.read')(self.authuser, self.repo_name):
             self.allow_push = False
         else:
-            self.exit('Access to %r denied' % safe_str(self.repo_name))
+            self.exit('Access to %r denied' % self.repo_name)
 
         self.db_repo = Repository.get_by_repo_name(self.repo_name)
         if self.db_repo is None:
--- a/kallithea/lib/vcs/conf/settings.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/conf/settings.py	Thu Feb 06 01:19:23 2020 +0100
@@ -18,7 +18,7 @@
 if os.path.isdir(VCSRC_PATH):
     VCSRC_PATH = os.path.join(VCSRC_PATH, '__init__.py')
 
-# list of default encoding used in safe_unicode/safe_str methods
+# list of default encoding used in safe_str/safe_bytes methods
 DEFAULT_ENCODINGS = aslist('utf-8')
 
 # path to git executable run by run_git_command function
--- a/kallithea/lib/vcs/nodes.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/nodes.py	Thu Feb 06 01:19:23 2020 +0100
@@ -9,13 +9,14 @@
     :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 """
 
+import functools
 import mimetypes
 import posixpath
 import stat
 
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.exceptions import NodeError, RemovedFileNodeError
-from kallithea.lib.vcs.utils import safe_str, safe_unicode
+from kallithea.lib.vcs.utils import safe_bytes, safe_str
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 
 
@@ -44,11 +45,9 @@
         self.cs = cs
         self.current_paths = current_paths
 
-    def __call__(self):
-        return [n for n in self]
-
-    def __getslice__(self, i, j):
-        for p in self.current_paths[i:j]:
+    def __getitem__(self, key):
+        assert isinstance(key, slice), key
+        for p in self.current_paths[key]:
             yield self.cs.get_node(p)
 
     def __len__(self):
@@ -81,11 +80,13 @@
         for p in self.current_paths:
             yield RemovedFileNode(path=p)
 
-    def __getslice__(self, i, j):
-        for p in self.current_paths[i:j]:
+    def __getitem__(self, key):
+        assert isinstance(key, slice), key
+        for p in self.current_paths[key]:
             yield RemovedFileNode(path=p)
 
 
+@functools.total_ordering
 class Node(object):
     """
     Simplest class representing file or directory on repository.  SCM backends
@@ -101,7 +102,7 @@
         if path.startswith('/'):
             raise NodeError("Cannot initialize Node objects with slash at "
                             "the beginning as only relative paths are supported")
-        self.path = safe_str(path.rstrip('/'))  # we store paths as str
+        self.path = path.rstrip('/')
         if path == '' and kind != NodeKind.DIR:
             raise NodeError("Only DirNode and its subclasses may be "
                             "initialized with empty path")
@@ -120,16 +121,12 @@
         return None
 
     @LazyProperty
-    def unicode_path(self):
-        return safe_unicode(self.path)
-
-    @LazyProperty
     def name(self):
         """
         Returns name of the node so if its path
         then only last part is returned.
         """
-        return safe_unicode(self.path.rstrip('/').split('/')[-1])
+        return self.path.rstrip('/').split('/')[-1]
 
     def _get_kind(self):
         return self._kind
@@ -145,42 +142,41 @@
 
     kind = property(_get_kind, _set_kind)
 
-    def __cmp__(self, other):
-        """
-        Comparator using name of the node, needed for quick list sorting.
-        """
-        kind_cmp = cmp(self.kind, other.kind)
-        if kind_cmp:
-            return kind_cmp
-        return cmp(self.name, other.name)
-
     def __eq__(self, other):
-        for attr in ['name', 'path', 'kind']:
-            if getattr(self, attr) != getattr(other, attr):
-                return False
+        if type(self) is not type(other):
+            return False
+        if self._kind != other._kind:
+            return False
+        if self.path != other.path:
+            return False
         if self.is_file():
-            if self.content != other.content:
-                return False
+            return self.content == other.content
         else:
             # For DirNode's check without entering each dir
             self_nodes_paths = list(sorted(n.path for n in self.nodes))
             other_nodes_paths = list(sorted(n.path for n in self.nodes))
-            if self_nodes_paths != other_nodes_paths:
-                return False
-        return True
+            return self_nodes_paths == other_nodes_paths
 
-    def __nq__(self, other):
-        return not self.__eq__(other)
+    def __lt__(self, other):
+        if self._kind < other._kind:
+            return True
+        if self._kind > other._kind:
+            return False
+        if self.path < other.path:
+            return True
+        if self.path > other.path:
+            return False
+        if self.is_file():
+            return self.content < other.content
+        else:
+            # For DirNode's check without entering each dir
+            self_nodes_paths = list(sorted(n.path for n in self.nodes))
+            other_nodes_paths = list(sorted(n.path for n in self.nodes))
+            return self_nodes_paths < other_nodes_paths
 
     def __repr__(self):
         return '<%s %r>' % (self.__class__.__name__, self.path)
 
-    def __str__(self):
-        return self.__repr__()
-
-    def __unicode__(self):
-        return self.name
-
     def get_parent_path(self):
         """
         Returns node's parent path or empty string if node is root.
@@ -258,8 +254,12 @@
             raise NodeError("Cannot use both content and changeset")
         super(FileNode, self).__init__(path, kind=NodeKind.FILE)
         self.changeset = changeset
+        if not isinstance(content, bytes) and content is not None:
+            # File content is one thing that inherently must be bytes ... but
+            # VCS module tries to be "user friendly" and support unicode ...
+            content = safe_bytes(content)
         self._content = content
-        self._mode = mode or 0100644
+        self._mode = mode or 0o100644
 
     @LazyProperty
     def mode(self):
@@ -273,25 +273,17 @@
             mode = self._mode
         return mode
 
-    def _get_content(self):
+    @property
+    def content(self):
+        """
+        Returns lazily byte content of the FileNode.
+        """
         if self.changeset:
             content = self.changeset.get_file_content(self.path)
         else:
             content = self._content
         return content
 
-    @property
-    def content(self):
-        """
-        Returns lazily content of the FileNode. If possible, would try to
-        decode content from UTF-8.
-        """
-        content = self._get_content()
-
-        if bool(content and '\0' in content):
-            return content
-        return safe_unicode(content)
-
     @LazyProperty
     def size(self):
         if self.changeset:
@@ -329,8 +321,8 @@
                 encoding = None
 
                 # try with pygments
+                from pygments import lexers
                 try:
-                    from pygments import lexers
                     mt = lexers.get_lexer_for_filename(self.name).mimetypes
                 except lexers.ClassNotFound:
                     mt = None
@@ -361,7 +353,7 @@
         """
         from pygments import lexers
         try:
-            lexer = lexers.guess_lexer_for_filename(self.name, self.content, stripnl=False)
+            lexer = lexers.guess_lexer_for_filename(self.name, safe_str(self.content), stripnl=False)
         except lexers.ClassNotFound:
             lexer = lexers.TextLexer(stripnl=False)
         # returns first alias
@@ -409,8 +401,7 @@
         """
         Returns True if file has binary content.
         """
-        _bin = '\0' in self._get_content()
-        return _bin
+        return b'\0' in self.content
 
     def is_browser_compatible_image(self):
         return self.mimetype in [
@@ -595,12 +586,13 @@
     size = 0
 
     def __init__(self, name, url, changeset=None, alias=None):
+        # Note: Doesn't call Node.__init__!
         self.path = name
         self.kind = NodeKind.SUBMODULE
         self.alias = alias
         # we have to use emptyChangeset here since this can point to svn/git/hg
         # submodules we cannot get from repository
-        self.changeset = EmptyChangeset(str(changeset), alias=alias)
+        self.changeset = EmptyChangeset(changeset, alias=alias)
         self.url = url
 
     def __repr__(self):
@@ -613,5 +605,5 @@
         Returns name of the node so if its path
         then only last part is returned.
         """
-        org = safe_unicode(self.path.rstrip('/').split('/')[-1])
+        org = self.path.rstrip('/').rsplit('/', 1)[-1]
         return u'%s @ %s' % (org, self.changeset.short_id)
--- a/kallithea/lib/vcs/subprocessio.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/subprocessio.py	Thu Feb 06 01:19:23 2020 +0100
@@ -44,7 +44,7 @@
         if type(source) in (type(''), bytes, bytearray):  # string-like
             self.bytes = bytes(source)
         else:  # can be either file pointer or file-like
-            if type(source) in (int, long):  # file pointer it is
+            if isinstance(source, int):  # file pointer it is
                 # converting file descriptor (int) stdin into file-like
                 source = os.fdopen(source, 'rb', 16384)
             # let's see if source is file-like by now
@@ -125,11 +125,7 @@
             if len(t) > ccm:
                 kr.clear()
                 kr.wait(2)
-                # # this only works on 2.7.x and up
-                # if not kr.wait(10):
-                #     raise Exception("Timed out while waiting for input to be read.")
-                # instead we'll use this
-                if len(t) > ccm + 3:
+                if not kr.wait(10):
                     raise IOError(
                         "Timed out while waiting for input from subprocess.")
             t.append(b)
@@ -178,7 +174,7 @@
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         while not len(self.data) and not self.worker.EOF.is_set():
             self.worker.data_added.clear()
             self.worker.data_added.wait(0.2)
@@ -286,7 +282,7 @@
 
     - We are multithreaded. Writing in and reading out, err are all sep threads.
     - We support concurrent (in and out) stream processing.
-    - The output is not a stream. It's a queue of read string (bytes, not unicode)
+    - The output is not a stream. It's a queue of read string (bytes, not str)
       chunks. The object behaves as an iterable. You can "for chunk in obj:" us.
     - We are non-blocking in more respects than communicate()
       (reading from subprocess out pauses when internal buffer is full, but
@@ -367,18 +363,17 @@
             and returncode != 0
         ): # and it failed
             bg_out.stop()
-            out = ''.join(bg_out)
+            out = b''.join(bg_out)
             bg_err.stop()
-            err = ''.join(bg_err)
-            if (err.strip() == 'fatal: The remote end hung up unexpectedly' and
-                out.startswith('0034shallow ')
+            err = b''.join(bg_err)
+            if (err.strip() == b'fatal: The remote end hung up unexpectedly' and
+                out.startswith(b'0034shallow ')
             ):
                 # hack inspired by https://github.com/schacon/grack/pull/7
                 bg_out = iter([out])
                 _p = None
             elif err:
-                raise EnvironmentError(
-                    "Subprocess exited due to an error:\n" + err)
+                raise EnvironmentError("Subprocess exited due to an error: %s" % err)
             else:
                 raise EnvironmentError(
                     "Subprocess exited with non 0 ret code: %s" % returncode)
@@ -390,7 +385,7 @@
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         if self.process:
             returncode = self.process.poll()
             if (returncode is not None # process has terminated
@@ -400,7 +395,7 @@
                 self.error.stop()
                 err = ''.join(self.error)
                 raise EnvironmentError("Subprocess exited due to an error:\n" + err)
-        return self.output.next()
+        return next(self.output)
 
     def throw(self, type, value=None, traceback=None):
         if self.output.length or not self.output.done_reading:
--- a/kallithea/lib/vcs/utils/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/utils/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 """
 This module provides some useful tools for ``vcs`` like annotate/diff html
 output. It also includes some internal helpers.
@@ -25,7 +27,7 @@
     :param sep:
     :param strip:
     """
-    if isinstance(obj, (basestring)):
+    if isinstance(obj, str):
         lst = obj.split(sep)
         if strip:
             lst = [v.strip() for v in lst]
@@ -66,89 +68,107 @@
     return val
 
 
-def safe_unicode(str_, from_encoding=None):
+def safe_str(s):
     """
-    safe unicode function. Does few trick to turn str_ into unicode
-
-    In case of UnicodeDecode error we try to return it with encoding detected
-    by chardet library if it fails fallback to unicode with errors replaced
-
-    :param str_: string to decode
-    :rtype: unicode
-    :returns: unicode object
+    Safe unicode str function. Use a few tricks to turn s into str:
+    In case of UnicodeDecodeError with configured default encodings, try to
+    detect encoding with chardet library, then fall back to first encoding with
+    errors replaced.
     """
-    if isinstance(str_, unicode):
-        return str_
+    if isinstance(s, str):
+        return s
 
-    if not from_encoding:
-        from kallithea.lib.vcs.conf import settings
-        from_encoding = settings.DEFAULT_ENCODINGS
-
-    if not isinstance(from_encoding, (list, tuple)):
-        from_encoding = [from_encoding]
+    if not isinstance(s, bytes):  # use __str__ and don't expect UnicodeDecodeError
+        return str(s)
 
-    try:
-        return unicode(str_)
-    except UnicodeDecodeError:
-        pass
-
-    for enc in from_encoding:
+    from kallithea.lib.vcs.conf import settings
+    for enc in settings.DEFAULT_ENCODINGS:
         try:
-            return unicode(str_, enc)
+            return str(s, enc)
         except UnicodeDecodeError:
             pass
 
     try:
         import chardet
-        encoding = chardet.detect(str_)['encoding']
-        if encoding is None:
-            raise Exception()
-        return str_.decode(encoding)
-    except (ImportError, UnicodeDecodeError, Exception):
-        return unicode(str_, from_encoding[0], 'replace')
+        encoding = chardet.detect(s)['encoding']
+        if encoding is not None:
+            return s.decode(encoding)
+    except (ImportError, UnicodeDecodeError):
+        pass
+
+    return str(s, settings.DEFAULT_ENCODINGS[0], 'replace')
 
 
-def safe_str(unicode_, to_encoding=None):
+def safe_bytes(s):
     """
-    safe str function. Does few trick to turn unicode_ into string
-
-    In case of UnicodeEncodeError we try to return it with encoding detected
-    by chardet library if it fails fallback to string with errors replaced
-
-    :param unicode_: unicode to encode
-    :rtype: str
-    :returns: str object
+    Safe bytes function. Use a few tricks to turn s into bytes string:
+    In case of UnicodeEncodeError with configured default encodings, fall back
+    to first configured encoding with errors replaced.
     """
+    if isinstance(s, bytes):
+        return s
 
-    # if it's not basestr cast to str
-    if not isinstance(unicode_, basestring):
-        return str(unicode_)
-
-    if isinstance(unicode_, str):
-        return unicode_
+    assert isinstance(s, str), repr(s)  # bytes cannot coerse with __str__ or handle None or int
 
-    if not to_encoding:
-        from kallithea.lib.vcs.conf import settings
-        to_encoding = settings.DEFAULT_ENCODINGS
-
-    if not isinstance(to_encoding, (list, tuple)):
-        to_encoding = [to_encoding]
-
-    for enc in to_encoding:
+    from kallithea.lib.vcs.conf import settings
+    for enc in settings.DEFAULT_ENCODINGS:
         try:
-            return unicode_.encode(enc)
+            return s.encode(enc)
         except UnicodeEncodeError:
             pass
 
-    try:
-        import chardet
-        encoding = chardet.detect(unicode_)['encoding']
-        if encoding is None:
-            raise UnicodeEncodeError()
+    return s.encode(settings.DEFAULT_ENCODINGS[0], 'replace')
+
+
+def ascii_bytes(s):
+    """
+    Simple conversion from str to bytes, *assuming* all codepoints are
+    7-bit and it thus is pure ASCII.
+    Will fail badly with UnicodeError on invalid input.
+    This should be used where enocding and "safe" ambiguity should be avoided.
+    Where strings already have been encoded in other ways but still are unicode
+    string - for example to hex, base64, json, urlencoding, or are known to be
+    identifiers.
 
-        return unicode_.encode(encoding)
-    except (ImportError, UnicodeEncodeError):
-        return unicode_.encode(to_encoding[0], 'replace')
+    >>> ascii_bytes('a')
+    b'a'
+    >>> ascii_bytes(u'a')
+    b'a'
+    >>> ascii_bytes('å')
+    Traceback (most recent call last):
+    UnicodeEncodeError: 'ascii' codec can't encode character '\xe5' in position 0: ordinal not in range(128)
+    >>> ascii_bytes('å'.encode('utf8'))
+    Traceback (most recent call last):
+    AssertionError: b'\xc3\xa5'
+    """
+    assert isinstance(s, str), repr(s)
+    return s.encode('ascii')
+
+
+def ascii_str(s):
+    r"""
+    Simple conversion from bytes to str, *assuming* all codepoints are
+    7-bit and it thus is pure ASCII.
+    Will fail badly with UnicodeError on invalid input.
+    This should be used where enocding and "safe" ambiguity should be avoided.
+    Where strings are encoded but also in other ways are known to be ASCII, and
+    where a unicode string is wanted without caring about encoding. For example
+    to hex, base64, urlencoding, or are known to be identifiers.
+
+    >>> ascii_str(b'a')
+    'a'
+    >>> ascii_str(u'a')
+    Traceback (most recent call last):
+    AssertionError: 'a'
+    >>> ascii_str('å'.encode('utf8'))
+    Traceback (most recent call last):
+    UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
+    >>> ascii_str(u'å')
+    Traceback (most recent call last):
+    AssertionError: 'å'
+    """
+    assert isinstance(s, bytes), repr(s)
+    return s.decode('ascii')
 
 
 # Regex taken from http://www.regular-expressions.info/email.html
@@ -178,7 +198,7 @@
     m = email_re.search(author)
     if m is None:
         return ''
-    return safe_str(m.group(0))
+    return m.group(0)
 
 
 def author_name(author):
--- a/kallithea/lib/vcs/utils/annotate.py	Sun Jan 05 01:19:05 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-import StringIO
-
-from pygments import highlight
-from pygments.formatters import HtmlFormatter
-
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.nodes import FileNode
-
-
-def annotate_highlight(filenode, annotate_from_changeset_func=None,
-        order=None, headers=None, **options):
-    """
-    Returns html portion containing annotated table with 3 columns: line
-    numbers, changeset information and pygmentized line of code.
-
-    :param filenode: FileNode object
-    :param annotate_from_changeset_func: function taking changeset and
-      returning single annotate cell; needs break line at the end
-    :param order: ordered sequence of ``ls`` (line numbers column),
-      ``annotate`` (annotate column), ``code`` (code column); Default is
-      ``['ls', 'annotate', 'code']``
-    :param headers: dictionary with headers (keys are whats in ``order``
-      parameter)
-    """
-    options['linenos'] = True
-    formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
-        headers=headers,
-        annotate_from_changeset_func=annotate_from_changeset_func, **options)
-    lexer = filenode.lexer
-    highlighted = highlight(filenode.content, lexer, formatter)
-    return highlighted
-
-
-class AnnotateHtmlFormatter(HtmlFormatter):
-
-    def __init__(self, filenode, annotate_from_changeset_func=None,
-            order=None, **options):
-        """
-        If ``annotate_from_changeset_func`` is passed it should be a function
-        which returns string from the given changeset. For example, we may pass
-        following function as ``annotate_from_changeset_func``::
-
-            def changeset_to_anchor(changeset):
-                return '<a href="/changesets/%s/">%s</a>\n' % \
-                       (changeset.id, changeset.id)
-
-        :param annotate_from_changeset_func: see above
-        :param order: (default: ``['ls', 'annotate', 'code']``); order of
-          columns;
-        :param options: standard pygment's HtmlFormatter options, there is
-          extra option tough, ``headers``. For instance we can pass::
-
-             formatter = AnnotateHtmlFormatter(filenode, headers={
-                'ls': '#',
-                'annotate': 'Annotate',
-                'code': 'Code',
-             })
-
-        """
-        super(AnnotateHtmlFormatter, self).__init__(**options)
-        self.annotate_from_changeset_func = annotate_from_changeset_func
-        self.order = order or ('ls', 'annotate', 'code')
-        headers = options.pop('headers', None)
-        if headers and not ('ls' in headers and 'annotate' in headers and
-            'code' in headers
-        ):
-            raise ValueError("If headers option dict is specified it must "
-                "all 'ls', 'annotate' and 'code' keys")
-        self.headers = headers
-        if isinstance(filenode, FileNode):
-            self.filenode = filenode
-        else:
-            raise VCSError("This formatter expect FileNode parameter, not %r"
-                % type(filenode))
-
-    def annotate_from_changeset(self, changeset):
-        """
-        Returns full html line for single changeset per annotated line.
-        """
-        if self.annotate_from_changeset_func:
-            return self.annotate_from_changeset_func(changeset)
-        else:
-            return ''.join((changeset.id, '\n'))
-
-    def _wrap_tablelinenos(self, inner):
-        dummyoutfile = StringIO.StringIO()
-        lncount = 0
-        for t, line in inner:
-            if t:
-                lncount += 1
-            dummyoutfile.write(line)
-
-        fl = self.linenostart
-        mw = len(str(lncount + fl - 1))
-        sp = self.linenospecial
-        st = self.linenostep
-        la = self.lineanchors
-        aln = self.anchorlinenos
-        if sp:
-            lines = []
-
-            for i in range(fl, fl + lncount):
-                if i % st == 0:
-                    if i % sp == 0:
-                        if aln:
-                            lines.append('<a href="#%s-%d" class="special">'
-                                         '%*d</a>' %
-                                         (la, i, mw, i))
-                        else:
-                            lines.append('<span class="special">'
-                                         '%*d</span>' % (mw, i))
-                    else:
-                        if aln:
-                            lines.append('<a href="#%s-%d">'
-                                         '%*d</a>' % (la, i, mw, i))
-                        else:
-                            lines.append('%*d' % (mw, i))
-                else:
-                    lines.append('')
-            ls = '\n'.join(lines)
-        else:
-            lines = []
-            for i in range(fl, fl + lncount):
-                if i % st == 0:
-                    if aln:
-                        lines.append('<a href="#%s-%d">%*d</a>'
-                                     % (la, i, mw, i))
-                    else:
-                        lines.append('%*d' % (mw, i))
-                else:
-                    lines.append('')
-            ls = '\n'.join(lines)
-
-        annotate_changesets = [tup[1] for tup in self.filenode.annotate]
-        # If pygments cropped last lines break we need do that too
-        ln_cs = len(annotate_changesets)
-        ln_ = len(ls.splitlines())
-        if ln_cs > ln_:
-            annotate_changesets = annotate_changesets[:ln_ - ln_cs]
-        annotate = ''.join((self.annotate_from_changeset(changeset)
-            for changeset in annotate_changesets))
-        # in case you wonder about the seemingly redundant <div> here:
-        # since the content in the other cell also is wrapped in a div,
-        # some browsers in some configurations seem to mess up the formatting.
-        '''
-        yield 0, ('<table class="%stable">' % self.cssclass +
-                  '<tr><td class="linenos"><div class="linenodiv"><pre>' +
-                  ls + '</pre></div></td>' +
-                  '<td class="code">')
-        yield 0, dummyoutfile.getvalue()
-        yield 0, '</td></tr></table>'
-
-        '''
-        headers_row = []
-        if self.headers:
-            headers_row = ['<tr class="annotate-header">']
-            for key in self.order:
-                td = ''.join(('<td>', self.headers[key], '</td>'))
-                headers_row.append(td)
-            headers_row.append('</tr>')
-
-        body_row_start = ['<tr>']
-        for key in self.order:
-            if key == 'ls':
-                body_row_start.append(
-                    '<td class="linenos"><div class="linenodiv"><pre>' +
-                    ls + '</pre></div></td>')
-            elif key == 'annotate':
-                body_row_start.append(
-                    '<td class="annotate"><div class="annotatediv"><pre>' +
-                    annotate + '</pre></div></td>')
-            elif key == 'code':
-                body_row_start.append('<td class="code">')
-        yield 0, ('<table class="%stable">' % self.cssclass +
-                  ''.join(headers_row) +
-                  ''.join(body_row_start)
-                  )
-        yield 0, dummyoutfile.getvalue()
-        yield 0, '</td></tr></table>'
--- a/kallithea/lib/vcs/utils/archivers.py	Sun Jan 05 01:19:05 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    vcs.utils.archivers
-    ~~~~~~~~~~~~~~~~~~~
-
-    set of archiver functions for creating archives from repository content
-
-    :created_on: Jan 21, 2011
-    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
-"""
-
-
-class BaseArchiver(object):
-
-    def __init__(self):
-        self.archive_file = self._get_archive_file()
-
-    def addfile(self):
-        """
-        Adds a file to archive container
-        """
-        pass
-
-    def close(self):
-        """
-        Closes and finalizes operation of archive container object
-        """
-        self.archive_file.close()
-
-    def _get_archive_file(self):
-        """
-        Returns container for specific archive
-        """
-        raise NotImplementedError()
-
-
-class TarArchiver(BaseArchiver):
-    pass
-
-
-class Tbz2Archiver(BaseArchiver):
-    pass
-
-
-class TgzArchiver(BaseArchiver):
-    pass
-
-
-class ZipArchiver(BaseArchiver):
-    pass
-
-
-def get_archiver(self, kind):
-    """
-    Returns instance of archiver class specific to given kind
-
-    :param kind: archive kind
-    """
-
-    archivers = {
-        'tar': TarArchiver,
-        'tbz2': Tbz2Archiver,
-        'tgz': TgzArchiver,
-        'zip': ZipArchiver,
-    }
-
-    return archivers[kind]()
--- a/kallithea/lib/vcs/utils/fakemod.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/utils/fakemod.py	Thu Feb 06 01:19:23 2020 +0100
@@ -9,5 +9,5 @@
     """
     module = imp.new_module(name)
     module.__file__ = path
-    execfile(path, module.__dict__)
+    exec(compile(open(path, "rb").read(), path, 'exec'), module.__dict__)
     return module
--- a/kallithea/lib/vcs/utils/helpers.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/utils/helpers.py	Thu Feb 06 01:19:23 2020 +0100
@@ -33,16 +33,14 @@
     if not os.path.isdir(path):
         raise VCSError("Given path %s is not a directory" % path)
 
-    def get_scms(path):
-        return [(scm, path) for scm in get_scms_for_path(path)]
-
-    found_scms = get_scms(path)
-    while not found_scms and search_up:
+    while True:
+        found_scms = [(scm, path) for scm in get_scms_for_path(path)]
+        if found_scms or not search_up:
+            break
         newpath = abspath(path, '..')
         if newpath == path:
             break
         path = newpath
-        found_scms = get_scms(path)
 
     if len(found_scms) > 1:
         for scm in found_scms:
@@ -133,7 +131,7 @@
         >>> parse_changesets('aaabbb')
         {'start': None, 'main': 'aaabbb', 'end': None}
         >>> parse_changesets('aaabbb..cccddd')
-        {'start': 'aaabbb', 'main': None, 'end': 'cccddd'}
+        {'start': 'aaabbb', 'end': 'cccddd', 'main': None}
 
     """
     text = text.strip()
--- a/kallithea/lib/vcs/utils/hgcompat.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/utils/hgcompat.py	Thu Feb 06 01:19:23 2020 +0100
@@ -2,46 +2,12 @@
 Mercurial libs compatibility
 """
 
-# Mercurial 5.0 550a172a603b renamed memfilectx argument `copied` to `copysource`
-import inspect
-
-import mercurial
-from mercurial import archival, config, demandimport, discovery, httppeer, localrepo
-from mercurial import merge as hg_merge
-from mercurial import obsutil, patch, scmutil, sshpeer, ui, unionrepo
-from mercurial.commands import clone, nullid, pull
-from mercurial.context import memctx, memfilectx
-from mercurial.discovery import findcommonoutgoing
-from mercurial.encoding import tolocal
-from mercurial.error import Abort, RepoError, RepoLookupError
-from mercurial.hg import peer
-from mercurial.hgweb import hgweb_mod
-from mercurial.hgweb.common import get_contact
-from mercurial.match import exact as match_exact
-from mercurial.match import match
-from mercurial.mdiff import diffopts
-from mercurial.node import hex, nullrev
-from mercurial.scmutil import revrange
-from mercurial.tags import tag
-from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
-from mercurial.util import url as hg_url
+import mercurial.localrepo
 
 
-# patch demandimport, due to bug in mercurial when it always triggers demandimport.enable()
-demandimport.enable = lambda *args, **kwargs: 1
-
-
-# workaround for 3.3 94ac64bcf6fe and not calling largefiles reposetup correctly
-localrepo.localrepository._lfstatuswriters = [lambda *msg, **opts: None]
-# 3.5 7699d3212994 added the invariant that repo.lfstatus must exist before hitting overridearchive
-localrepo.localrepository.lfstatus = False
-
-if inspect.getargspec(memfilectx.__init__).args[7] != 'copysource':
-    assert inspect.getargspec(memfilectx.__init__).args[7] == 'copied', inspect.getargspec(memfilectx.__init__).args
-    __org_memfilectx_ = memfilectx
-    memfilectx = lambda repo, changectx, path, data, islink=False, isexec=False, copysource=None: \
-        __org_memfilectx_(repo, changectx, path, data, islink=islink, isexec=isexec, copied=copysource)
-
-# Mercurial 5.0 dropped exact argument for match in 635a12c53ea6, and 0531dff73d0b made the exact function stable with a single parameter
-if inspect.getargspec(match_exact).args[0] != 'files':
-    match_exact = lambda path: match(None, '', [path], exact=True)
+def monkey_do():
+    """Apply some Mercurial monkey patching"""
+    # workaround for 3.3 94ac64bcf6fe and not calling largefiles reposetup correctly, and test_archival failing
+    mercurial.localrepo.localrepository._lfstatuswriters = [lambda *msg, **opts: None]
+    # 3.5 7699d3212994 added the invariant that repo.lfstatus must exist before hitting overridearchive
+    mercurial.localrepo.localrepository.lfstatus = False
--- a/kallithea/lib/vcs/utils/progressbar.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/utils/progressbar.py	Thu Feb 06 01:19:23 2020 +0100
@@ -43,7 +43,7 @@
     def __iter__(self):
         start = self.step
         end = self.steps + 1
-        for x in xrange(start, end):
+        for x in range(start, end):
             self.render(x)
             yield x
 
@@ -215,7 +215,7 @@
     code_list = []
     if text == '' and len(opts) == 1 and opts[0] == 'reset':
         return '\x1b[%sm' % RESET
-    for k, v in kwargs.iteritems():
+    for k, v in kwargs.items():
         if k == 'fg':
             code_list.append(foreground[v])
         elif k == 'bg':
@@ -359,7 +359,7 @@
 
     print("Standard progress bar...")
     bar = ProgressBar(30)
-    for x in xrange(1, 31):
+    for x in range(1, 31):
         bar.render(x)
         time.sleep(0.02)
     bar.stream.write('\n')
@@ -410,7 +410,7 @@
     bar.width = 50
     bar.elements.remove('steps')
     bar.elements += ['transfer', 'time', 'eta', 'speed']
-    for x in xrange(0, bar.steps, 1024):
+    for x in range(0, bar.steps, 1024):
         bar.render(x)
         time.sleep(0.01)
         now = datetime.datetime.now()
--- a/kallithea/lib/vcs/utils/termcolors.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/lib/vcs/utils/termcolors.py	Thu Feb 06 01:19:23 2020 +0100
@@ -44,7 +44,7 @@
     code_list = []
     if text == '' and len(opts) == 1 and opts[0] == 'reset':
         return '\x1b[%sm' % RESET
-    for k, v in kwargs.iteritems():
+    for k, v in kwargs.items():
         if k == 'fg':
             code_list.append(foreground[v])
         elif k == 'bg':
@@ -188,7 +188,7 @@
                 definition['bg'] = colors[-1]
 
             # All remaining instructions are options
-            opts = tuple(s for s in styles if s in opt_dict.keys())
+            opts = tuple(s for s in styles if s in opt_dict)
             if opts:
                 definition['opts'] = opts
 
--- a/kallithea/model/comment.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/comment.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,7 +31,7 @@
 from tg.i18n import ugettext as _
 
 from kallithea.lib import helpers as h
-from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode
+from kallithea.lib.utils2 import extract_mentioned_users
 from kallithea.model.db import ChangesetComment, PullRequest, Repository, User
 from kallithea.model.meta import Session
 from kallithea.model.notification import NotificationModel
@@ -81,11 +81,10 @@
                 repo_name=repo.repo_name,
                 revision=revision,
                 anchor='comment-%s' % comment.comment_id)
-            subj = safe_unicode(
-                h.link_to('Re changeset: %(desc)s %(line)s' %
+            subj = h.link_to(
+                'Re changeset: %(desc)s %(line)s' %
                           {'desc': desc, 'line': line},
-                          comment_url)
-            )
+                 comment_url)
             # get the current participants of this changeset
             recipients = _list_changeset_commenters(revision)
             # add changeset author if it's known locally
@@ -127,13 +126,12 @@
                                                           h.canonical_hostname()))
             comment_url = pull_request.url(canonical=True,
                 anchor='comment-%s' % comment.comment_id)
-            subj = safe_unicode(
-                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' %
+            subj = h.link_to(
+                'Re pull request %(pr_nice_id)s: %(desc)s %(line)s' %
                           {'desc': desc,
                            'pr_nice_id': comment.pull_request.nice_id(),
                            'line': line},
-                          comment_url)
-            )
+                comment_url)
             # get the current participants of this pull request
             recipients = _list_pull_request_commenters(pull_request)
             recipients.append(pull_request.owner)
@@ -257,7 +255,7 @@
         paths = defaultdict(lambda: defaultdict(list))
         for co in comments:
             paths[co.f_path][co.line_no].append(co)
-        return paths.items()
+        return sorted(paths.items())
 
     def _get_comments(self, repo_id, revision=None, pull_request=None,
                 inline=False, f_path=None, line_no=None):
--- a/kallithea/model/db.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/db.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,6 +25,7 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
+import base64
 import collections
 import datetime
 import functools
@@ -37,17 +38,18 @@
 import ipaddr
 import sqlalchemy
 from beaker.cache import cache_region, region_invalidate
-from sqlalchemy import *
+from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Index, Integer, LargeBinary, String, Unicode, UnicodeText, UniqueConstraint
 from sqlalchemy.ext.hybrid import hybrid_property
 from sqlalchemy.orm import class_mapper, joinedload, relationship, validates
 from tg.i18n import lazy_ugettext as _
 from webob.exc import HTTPNotFound
 
 import kallithea
+from kallithea.lib import ext_json
 from kallithea.lib.caching_query import FromCache
-from kallithea.lib.compat import json
 from kallithea.lib.exceptions import DefaultUserException
-from kallithea.lib.utils2 import Optional, aslist, get_changeset_safe, get_clone_url, remove_prefix, safe_int, safe_str, safe_unicode, str2bool, urlreadable
+from kallithea.lib.utils2 import (
+    Optional, ascii_bytes, aslist, get_changeset_safe, get_clone_url, remove_prefix, safe_bytes, safe_int, safe_str, str2bool, urlreadable)
 from kallithea.lib.vcs import get_backend
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.utils.helpers import get_scm
@@ -62,7 +64,8 @@
 # BASE CLASSES
 #==============================================================================
 
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+def _hash_key(k):
+    return hashlib.md5(safe_bytes(k)).hexdigest()
 
 
 class BaseDbModel(object):
@@ -73,6 +76,7 @@
     @classmethod
     def _get_keys(cls):
         """return column names for this model """
+        # Note: not a normal dict - iterator gives "users.firstname", but keys gives "firstname"
         return class_mapper(cls).c.keys()
 
     def get_dict(self):
@@ -90,7 +94,7 @@
             # update with attributes from __json__
             if callable(_json_attr):
                 _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
+            for k, val in _json_attr.items():
                 d[k] = val
         return d
 
@@ -135,8 +139,10 @@
             return None
         if isinstance(value, cls):
             return value
-        if isinstance(value, (int, long)) or safe_str(value).isdigit():
+        if isinstance(value, int):
             return cls.get(value)
+        if isinstance(value, str) and value.isdigit():
+            return cls.get(int(value))
         if callback is not None:
             return callback(value)
 
@@ -163,12 +169,6 @@
         Session().delete(obj)
 
     def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            try:
-                return safe_str(self.__unicode__())
-            except UnicodeDecodeError:
-                pass
         return '<DB:%s>' % (self.__class__.__name__)
 
 
@@ -185,9 +185,9 @@
     )
 
     SETTINGS_TYPES = {
-        'str': safe_str,
+        'str': safe_bytes,
         'int': safe_int,
-        'unicode': safe_unicode,
+        'unicode': safe_str,
         'bool': str2bool,
         'list': functools.partial(aslist, sep=',')
     }
@@ -205,7 +205,7 @@
 
     @validates('_app_settings_value')
     def validate_settings_value(self, key, val):
-        assert type(val) == unicode
+        assert isinstance(val, str)
         return val
 
     @hybrid_property
@@ -218,11 +218,9 @@
     @app_settings_value.setter
     def app_settings_value(self, val):
         """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
+        Setter that will always make sure we use str in app_settings_value
         """
-        self._app_settings_value = safe_unicode(val)
+        self._app_settings_value = safe_str(val)
 
     @hybrid_property
     def app_settings_type(self):
@@ -232,13 +230,13 @@
     def app_settings_type(self, val):
         if val not in self.SETTINGS_TYPES:
             raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
+                            % (list(self.SETTINGS_TYPES), val))
         self._app_settings_type = val
 
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
+    def __repr__(self):
+        return "<%s %s.%s=%r>" % (
             self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
+            self.app_settings_name, self.app_settings_type, self.app_settings_value
         )
 
     @classmethod
@@ -328,9 +326,9 @@
         info = {
             'modules': sorted(mods, key=lambda k: k[0].lower()),
             'py_version': platform.python_version(),
-            'platform': safe_unicode(platform.platform()),
+            'platform': platform.platform(),
             'kallithea_version': kallithea.__version__,
-            'git_version': safe_unicode(check_git_version()),
+            'git_version': str(check_git_version()),
             'git_path': kallithea.CONFIG.get('git_path')
         }
         return info
@@ -394,8 +392,9 @@
         new_ui.ui_value = val
 
     def __repr__(self):
-        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
-                                    self.ui_key, self.ui_value)
+        return '<%s %s.%s=%r>' % (
+            self.__class__.__name__,
+            self.ui_section, self.ui_key, self.ui_value)
 
 
 class User(Base, BaseDbModel):
@@ -520,20 +519,19 @@
             return {}
 
         try:
-            return json.loads(self._user_data)
+            return ext_json.loads(self._user_data)
         except TypeError:
             return {}
 
     @user_data.setter
     def user_data(self, val):
         try:
-            self._user_data = json.dumps(val)
+            self._user_data = ascii_bytes(ext_json.dumps(val))
         except Exception:
             log.error(traceback.format_exc())
 
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.user_id, self.username)
+    def __repr__(self):
+        return "<%s %s: %r')>" % (self.__class__.__name__, self.user_id, self.username)
 
     @classmethod
     def guess_instance(cls, value):
@@ -567,7 +565,7 @@
     @classmethod
     def get_by_username(cls, username, case_insensitive=False, cache=False):
         if case_insensitive:
-            q = cls.query().filter(func.lower(cls.username) == func.lower(username))
+            q = cls.query().filter(sqlalchemy.func.lower(cls.username) == sqlalchemy.func.lower(username))
         else:
             q = cls.query().filter(cls.username == username)
 
@@ -602,7 +600,7 @@
 
     @classmethod
     def get_by_email(cls, email, cache=False):
-        q = cls.query().filter(func.lower(cls.email) == func.lower(email))
+        q = cls.query().filter(sqlalchemy.func.lower(cls.email) == sqlalchemy.func.lower(email))
 
         if cache:
             q = q.options(FromCache("sql_cache_short",
@@ -612,7 +610,7 @@
         if ret is None:
             q = UserEmailMap.query()
             # try fetching in alternate email map
-            q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
+            q = q.filter(sqlalchemy.func.lower(UserEmailMap.email) == sqlalchemy.func.lower(email))
             q = q.options(joinedload(UserEmailMap.user))
             if cache:
                 q = q.options(FromCache("sql_cache_short",
@@ -772,9 +770,8 @@
           ip_range=self._get_ip_range(self.ip_addr)
         )
 
-    def __unicode__(self):
-        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
-                                            self.user_id, self.ip_addr)
+    def __repr__(self):
+        return "<%s %s: %s>" % (self.__class__.__name__, self.user_id, self.ip_addr)
 
 
 class UserLog(Base, BaseDbModel):
@@ -792,10 +789,10 @@
     action = Column(UnicodeText(), nullable=False)
     action_date = Column(DateTime(timezone=False), nullable=False)
 
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
+    def __repr__(self):
+        return "<%s %r: %r')>" % (self.__class__.__name__,
+                                  self.repository_name,
+                                  self.action)
 
     @property
     def action_as_day(self):
@@ -834,21 +831,21 @@
             return {}
 
         try:
-            return json.loads(self._group_data)
+            return ext_json.loads(self._group_data)
         except TypeError:
             return {}
 
     @group_data.setter
     def group_data(self, val):
         try:
-            self._group_data = json.dumps(val)
+            self._group_data = ascii_bytes(ext_json.dumps(val))
         except Exception:
             log.error(traceback.format_exc())
 
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
+    def __repr__(self):
+        return "<%s %s: %r')>" % (self.__class__.__name__,
+                                  self.users_group_id,
+                                  self.users_group_name)
 
     @classmethod
     def guess_instance(cls, value):
@@ -858,7 +855,7 @@
     def get_by_group_name(cls, group_name, cache=False,
                           case_insensitive=False):
         if case_insensitive:
-            q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
+            q = cls.query().filter(sqlalchemy.func.lower(cls.users_group_name) == sqlalchemy.func.lower(group_name))
         else:
             q = cls.query().filter(cls.users_group_name == group_name)
         if cache:
@@ -1009,9 +1006,9 @@
                     primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
                     cascade="all, delete-orphan")
 
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
+    def __repr__(self):
+        return "<%s %s: %r>" % (self.__class__.__name__,
+                                  self.repo_id, self.repo_name)
 
     @hybrid_property
     def landing_rev(self):
@@ -1033,7 +1030,7 @@
     @hybrid_property
     def changeset_cache(self):
         try:
-            cs_cache = json.loads(self._changeset_cache) # might raise on bad data
+            cs_cache = ext_json.loads(self._changeset_cache) # might raise on bad data
             cs_cache['raw_id'] # verify data, raise exception on error
             return cs_cache
         except (TypeError, KeyError, ValueError):
@@ -1042,7 +1039,7 @@
     @changeset_cache.setter
     def changeset_cache(self, val):
         try:
-            self._changeset_cache = json.dumps(val)
+            self._changeset_cache = ascii_bytes(ext_json.dumps(val))
         except Exception:
             log.error(traceback.format_exc())
 
@@ -1055,7 +1052,7 @@
         q = super(Repository, cls).query()
 
         if sorted:
-            q = q.order_by(func.lower(Repository.repo_name))
+            q = q.order_by(sqlalchemy.func.lower(Repository.repo_name))
 
         return q
 
@@ -1083,7 +1080,7 @@
         """Get the repo, defaulting to database case sensitivity.
         case_insensitive will be slower and should only be specified if necessary."""
         if case_insensitive:
-            q = Session().query(cls).filter(func.lower(cls.repo_name) == func.lower(repo_name))
+            q = Session().query(cls).filter(sqlalchemy.func.lower(cls.repo_name) == sqlalchemy.func.lower(repo_name))
         else:
             q = Session().query(cls).filter(cls.repo_name == repo_name)
         q = q.options(joinedload(Repository.fork)) \
@@ -1163,7 +1160,7 @@
         # names in the database, but that eventually needs to be converted
         # into a valid system path
         p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
+        return os.path.join(*p)
 
     @property
     def cache_keys(self):
@@ -1190,7 +1187,7 @@
         Creates an db based ui object for this repository
         """
         from kallithea.lib.utils import make_ui
-        return make_ui(clear_session=False)
+        return make_ui()
 
     @classmethod
     def is_valid(cls, repo_name):
@@ -1425,7 +1422,7 @@
         return _c(rn)
 
     def scm_instance_no_cache(self):
-        repo_full_path = safe_str(self.repo_full_path)
+        repo_full_path = self.repo_full_path
         alias = get_scm(repo_full_path)[0]
         log.debug('Creating instance of %s repository from %s',
                   alias, self.repo_full_path)
@@ -1476,7 +1473,7 @@
         q = super(RepoGroup, cls).query()
 
         if sorted:
-            q = q.order_by(func.lower(RepoGroup.group_name))
+            q = q.order_by(sqlalchemy.func.lower(RepoGroup.group_name))
 
         return q
 
@@ -1484,9 +1481,9 @@
         self.group_name = group_name
         self.parent_group = parent_group
 
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
+    def __repr__(self):
+        return "<%s %s: %s>" % (self.__class__.__name__,
+                                self.group_id, self.group_name)
 
     @classmethod
     def _generate_choice(cls, repo_group):
@@ -1515,7 +1512,7 @@
         group_name = group_name.rstrip('/')
         if case_insensitive:
             gr = cls.query() \
-                .filter(func.lower(cls.group_name) == func.lower(group_name))
+                .filter(sqlalchemy.func.lower(cls.group_name) == sqlalchemy.func.lower(group_name))
         else:
             gr = cls.query() \
                 .filter(cls.group_name == group_name)
@@ -1731,8 +1728,8 @@
     permission_id = Column(Integer(), primary_key=True)
     permission_name = Column(String(255), nullable=False)
 
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
+    def __repr__(self):
+        return "<%s %s: %r>" % (
             self.__class__.__name__, self.permission_id, self.permission_name
         )
 
@@ -1797,8 +1794,9 @@
         Session().add(n)
         return n
 
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
+    def __repr__(self):
+        return '<%s %s at %s: %s>' % (
+            self.__class__.__name__, self.user, self.repository, self.permission)
 
 
 class UserUserGroupToPerm(Base, BaseDbModel):
@@ -1826,8 +1824,9 @@
         Session().add(n)
         return n
 
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
+    def __repr__(self):
+        return '<%s %s at %s: %s>' % (
+            self.__class__.__name__, self.user, self.user_group, self.permission)
 
 
 class UserToPerm(Base, BaseDbModel):
@@ -1844,8 +1843,9 @@
     user = relationship('User')
     permission = relationship('Permission')
 
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
+    def __repr__(self):
+        return '<%s %s: %s>' % (
+            self.__class__.__name__, self.user, self.permission)
 
 
 class UserGroupRepoToPerm(Base, BaseDbModel):
@@ -1873,8 +1873,9 @@
         Session().add(n)
         return n
 
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
+    def __repr__(self):
+        return '<%s %s at %s: %s>' % (
+            self.__class__.__name__, self.users_group, self.repository, self.permission)
 
 
 class UserGroupUserGroupToPerm(Base, BaseDbModel):
@@ -1902,8 +1903,9 @@
         Session().add(n)
         return n
 
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
+    def __repr__(self):
+        return '<%s %s at %s: %s>' % (
+            self.__class__.__name__, self.user_group, self.target_user_group, self.permission)
 
 
 class UserGroupToPerm(Base, BaseDbModel):
@@ -2006,7 +2008,7 @@
     user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
 
     follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by=lambda: func.lower(Repository.repo_name))
+    follows_repository = relationship('Repository', order_by=lambda: sqlalchemy.func.lower(Repository.repo_name))
 
     @classmethod
     def get_repo_followers(cls, repo_id):
@@ -2035,8 +2037,8 @@
         self.cache_args = repo_name
         self.cache_active = False
 
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
+    def __repr__(self):
+        return "<%s %s: %s=%s" % (
             self.__class__.__name__,
             self.cache_id, self.cache_key, self.cache_active)
 
@@ -2087,11 +2089,11 @@
         """
         inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
         log.debug('for repo %s got %s invalidation objects',
-                  safe_str(repo_name), inv_objs)
+                  repo_name, inv_objs)
 
         for inv_obj in inv_objs:
             log.debug('marking %s key for invalidation based on repo_name=%s',
-                      inv_obj, safe_str(repo_name))
+                      inv_obj, repo_name)
             Session().delete(inv_obj)
         Session().commit()
 
@@ -2225,8 +2227,8 @@
     comment = relationship('ChangesetComment')
     pull_request = relationship('PullRequest')
 
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
+    def __repr__(self):
+        return "<%s %r by %r>" % (
             self.__class__.__name__,
             self.status, self.author
         )
@@ -2278,7 +2280,7 @@
 
     @revisions.setter
     def revisions(self, val):
-        self._revisions = safe_unicode(':'.join(val))
+        self._revisions = ':'.join(val)
 
     @property
     def org_ref_parts(self):
@@ -2446,7 +2448,9 @@
         return (self.gist_expires != -1) & (time.time() > self.gist_expires)
 
     def __repr__(self):
-        return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
+        return "<%s %s %s>" % (
+            self.__class__.__name__,
+            self.gist_type, self.gist_access_id)
 
     @classmethod
     def guess_instance(cls, value):
@@ -2511,14 +2515,12 @@
     def scm_instance(self):
         from kallithea.lib.vcs import get_repo
         base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
+        return get_repo(os.path.join(base_path, self.gist_access_id))
 
 
 class UserSshKeys(Base, BaseDbModel):
     __tablename__ = 'user_ssh_keys'
     __table_args__ = (
-        Index('usk_public_key_idx', 'public_key'),
         Index('usk_fingerprint_idx', 'fingerprint'),
         UniqueConstraint('fingerprint'),
         _table_args_default_dict
@@ -2544,5 +2546,5 @@
         # the full public key is too long to be suitable as database key - instead,
         # use fingerprints similar to 'ssh-keygen -E sha256 -lf ~/.ssh/id_rsa.pub'
         self._public_key = full_key
-        enc_key = full_key.split(" ")[1]
-        self.fingerprint = hashlib.sha256(enc_key.decode('base64')).digest().encode('base64').replace('\n', '').rstrip('=')
+        enc_key = safe_bytes(full_key.split(" ")[1])
+        self.fingerprint = base64.b64encode(hashlib.sha256(base64.b64decode(enc_key)).digest()).replace(b'\n', b'').rstrip(b'=').decode()
--- a/kallithea/model/forms.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/forms.py	Thu Feb 06 01:19:23 2020 +0100
@@ -238,7 +238,7 @@
     return _PasswordResetConfirmationForm
 
 
-def RepoForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(),
+def RepoForm(edit=False, old_data=None, supported_backends=BACKENDS,
              repo_groups=None, landing_revs=None):
     old_data = old_data or {}
     repo_groups = repo_groups or []
@@ -315,7 +315,7 @@
     return _RepoFieldForm
 
 
-def RepoForkForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(),
+def RepoForkForm(edit=False, old_data=None, supported_backends=BACKENDS,
                  repo_groups=None, landing_revs=None):
     old_data = old_data or {}
     repo_groups = repo_groups or []
@@ -435,7 +435,7 @@
     return _CustomDefaultPermissionsForm
 
 
-def DefaultsForm(edit=False, old_data=None, supported_backends=BACKENDS.keys()):
+def DefaultsForm(edit=False, old_data=None, supported_backends=BACKENDS):
     class _DefaultsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
--- a/kallithea/model/gist.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/gist.py	Thu Feb 06 01:19:23 2020 +0100
@@ -32,8 +32,8 @@
 import time
 import traceback
 
-from kallithea.lib.compat import json
-from kallithea.lib.utils2 import AttributeDict, safe_int, safe_unicode, time_to_datetime
+from kallithea.lib import ext_json
+from kallithea.lib.utils2 import AttributeDict, ascii_bytes, safe_int, time_to_datetime
 from kallithea.model.db import Gist, Session, User
 from kallithea.model.repo import RepoModel
 from kallithea.model.scm import ScmModel
@@ -45,12 +45,12 @@
 GIST_METADATA_FILE = '.rc_gist_metadata'
 
 
-def make_gist_id():
+def make_gist_access_id():
     """Generate a random, URL safe, almost certainly unique gist identifier."""
     rnd = random.SystemRandom() # use cryptographically secure system PRNG
     alphabet = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz'
     length = 20
-    return u''.join(rnd.choice(alphabet) for _ in xrange(length))
+    return u''.join(rnd.choice(alphabet) for _ in range(length))
 
 
 class GistModel(object):
@@ -82,7 +82,7 @@
             'gist_updated': time.time(),
         }
         with open(os.path.join(repo.path, '.hg', GIST_METADATA_FILE), 'wb') as f:
-            f.write(json.dumps(metadata))
+            f.write(ascii_bytes(ext_json.dumps(metadata)))
 
     def get_gist(self, gist):
         return Gist.guess_instance(gist)
@@ -108,7 +108,7 @@
         :param lifetime: in minutes, -1 == forever
         """
         owner = User.guess_instance(owner)
-        gist_id = make_gist_id()
+        gist_access_id = make_gist_access_id()
         lifetime = safe_int(lifetime, -1)
         gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
         log.debug('set GIST expiration date to: %s',
@@ -117,21 +117,19 @@
         # create the Database version
         gist = Gist()
         gist.gist_description = description
-        gist.gist_access_id = gist_id
+        gist.gist_access_id = gist_access_id
         gist.owner_id = owner.user_id
         gist.gist_expires = gist_expires
-        gist.gist_type = safe_unicode(gist_type)
+        gist.gist_type = gist_type
         Session().add(gist)
         Session().flush() # make database assign gist.gist_id
         if gist_type == Gist.GIST_PUBLIC:
             # use DB ID for easy to use GIST ID
-            gist_id = safe_unicode(gist.gist_id)
-            gist.gist_access_id = gist_id
+            gist.gist_access_id = str(gist.gist_id)
 
-        gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
-        log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
+        log.debug('Creating new %s GIST repo %s', gist_type, gist.gist_access_id)
         repo = RepoModel()._create_filesystem_repo(
-            repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC)
+            repo_name=gist.gist_access_id, repo_type='hg', repo_group=GIST_STORE_LOC)
 
         processed_mapping = {}
         for filename in gist_mapping:
@@ -155,7 +153,7 @@
 
         # fake Kallithea Repository object
         fake_repo = AttributeDict(dict(
-            repo_name=gist_repo_path,
+            repo_name=os.path.join(GIST_STORE_LOC, gist.gist_access_id),
             scm_instance_no_cache=lambda: repo,
         ))
         ScmModel().create_nodes(
@@ -219,7 +217,7 @@
 
         # fake Kallithea Repository object
         fake_repo = AttributeDict(dict(
-            repo_name=gist_repo.path,
+            repo_name=os.path.join(GIST_STORE_LOC, gist.gist_access_id),
             scm_instance_no_cache=lambda: gist_repo,
         ))
 
--- a/kallithea/model/notification.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/notification.py	Thu Feb 06 01:19:23 2020 +0100
@@ -35,7 +35,6 @@
 
 import kallithea
 from kallithea.lib import helpers as h
-from kallithea.lib.utils2 import safe_unicode
 from kallithea.model.db import User
 
 
@@ -178,14 +177,21 @@
         try:
             subj = tmpl % kwargs
         except KeyError as e:
-            log.error('error generating email subject for %r from %s: %s', type_, ','.join(self._subj_map.keys()), e)
+            log.error('error generating email subject for %r from %s: %s', type_, ', '.join(self._subj_map), e)
             raise
-        l = [safe_unicode(x) for x in [kwargs.get('status_change'), kwargs.get('closing_pr') and _('Closing')] if x]
-        if l:
+        # gmail doesn't do proper threading but will ignore leading square
+        # bracket content ... so that is where we put status info
+        bracket_tags = []
+        status_change = kwargs.get('status_change')
+        if status_change:
+            bracket_tags.append(str(status_change))  # apply str to evaluate LazyString before .join
+        if kwargs.get('closing_pr'):
+            bracket_tags.append(_('Closing'))
+        if bracket_tags:
             if subj.startswith('['):
-                subj = '[' + ', '.join(l) + ': ' + subj[1:]
+                subj = '[' + ', '.join(bracket_tags) + ': ' + subj[1:]
             else:
-                subj = '[' + ', '.join(l) + '] ' + subj
+                subj = '[' + ', '.join(bracket_tags) + '] ' + subj
         return subj
 
     def get_email_tmpl(self, type_, content_type, **kwargs):
--- a/kallithea/model/permission.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/permission.py	Thu Feb 06 01:19:23 2020 +0100
@@ -73,8 +73,7 @@
             return '.'.join(perm_name.split('.')[:1])
 
         perms = UserToPerm.query().filter(UserToPerm.user == user).all()
-        defined_perms_groups = map(_get_group,
-                                (x.permission.permission_name for x in perms))
+        defined_perms_groups = set(_get_group(x.permission.permission_name) for x in perms)
         log.debug('GOT ALREADY DEFINED:%s', perms)
         DEFAULT_PERMS = Permission.DEFAULT_USER_PERMISSIONS
 
--- a/kallithea/model/pull_request.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/pull_request.py	Thu Feb 06 01:19:23 2020 +0100
@@ -33,7 +33,7 @@
 from tg.i18n import ugettext as _
 
 from kallithea.lib import helpers as h
-from kallithea.lib.utils2 import extract_mentioned_users, safe_str, safe_unicode
+from kallithea.lib.utils2 import ascii_bytes, extract_mentioned_users
 from kallithea.model.db import ChangesetStatus, PullRequest, PullRequestReviewer, User
 from kallithea.model.meta import Session
 from kallithea.model.notification import NotificationModel
@@ -68,14 +68,12 @@
         threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
                                       pr.pull_request_id,
                                       h.canonical_hostname())]
-        subject = safe_unicode(
-            h.link_to(
-              _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') %
+        subject = h.link_to(
+            _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') %
                 {'user': user.username,
                  'pr_title': pr.title,
                  'pr_nice_id': pr.nice_id()},
-                pr_url)
-            )
+            pr_url)
         body = pr.description
         _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':')
         _other_ref_type, other_ref_name, _other_rev = pr.other_ref.split(':')
@@ -261,7 +259,7 @@
 
         if self.org_repo.scm_instance.alias == 'git':
             # create a ref under refs/pull/ so that commits don't get garbage-collected
-            self.org_repo.scm_instance._repo["refs/pull/%d/head" % pr.pull_request_id] = safe_str(self.org_rev)
+            self.org_repo.scm_instance._repo[b"refs/pull/%d/head" % pr.pull_request_id] = ascii_bytes(self.org_rev)
 
         # reset state to under-review
         from kallithea.model.changeset_status import ChangesetStatusModel
@@ -362,11 +360,11 @@
             infos.append(_('No changes found on %s %s since previous iteration.') % (org_ref_type, org_ref_name))
             # TODO: fail?
 
-        try:
-            title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', title).groups()
-            v = int(old_v) + 1
-        except (AttributeError, ValueError):
-            v = 2
+        v = 2
+        m = re.match(r'(.*)\(v(\d+)\)\s*$', title)
+        if m is not None:
+            title = m.group(1)
+            v = int(m.group(2)) + 1
         self.create_action.title = '%s (v%s)' % (title.strip(), v)
 
         # using a mail-like separator, insert new iteration info in description with latest first
--- a/kallithea/model/repo.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/repo.py	Thu Feb 06 01:19:23 2020 +0100
@@ -39,7 +39,7 @@
 from kallithea.lib.exceptions import AttachedForksError
 from kallithea.lib.hooks import log_delete_repository
 from kallithea.lib.utils import is_valid_repo_uri, make_ui
-from kallithea.lib.utils2 import LazyProperty, get_current_authuser, obfuscate_url_pw, remove_prefix, safe_str, safe_unicode
+from kallithea.lib.utils2 import LazyProperty, get_current_authuser, obfuscate_url_pw, remove_prefix
 from kallithea.lib.vcs.backends import get_backend
 from kallithea.model.db import (
     Permission, RepoGroup, Repository, RepositoryField, Session, Statistics, Ui, User, UserGroup, UserGroupRepoGroupToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm, UserRepoToPerm)
@@ -119,7 +119,6 @@
 
     @classmethod
     def _render_datatable(cls, tmpl, *args, **kwargs):
-        import kallithea
         from tg import tmpl_context as c, request, app_globals
         from tg.i18n import ugettext as _
 
@@ -128,7 +127,7 @@
 
         tmpl = template.get_def(tmpl)
         kwargs.update(dict(_=_, h=h, c=c, request=request))
-        return tmpl.render(*args, **kwargs)
+        return tmpl.render_unicode(*args, **kwargs)
 
     def get_repos_as_dict(self, repos_list, repo_groups_list=None,
                           admin=False,
@@ -290,7 +289,7 @@
                 # clone_uri is modified - if given a value, check it is valid
                 if clone_uri != '':
                     # will raise exception on error
-                    is_valid_repo_uri(cur_repo.repo_type, clone_uri, make_ui(clear_session=False))
+                    is_valid_repo_uri(cur_repo.repo_type, clone_uri, make_ui())
                 cur_repo.clone_uri = clone_uri
 
             if 'repo_name' in kwargs:
@@ -306,8 +305,7 @@
                     repo=cur_repo, user='default', perm=EMPTY_PERM
                 )
                 # handle extra fields
-            for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
-                                kwargs):
+            for field in [k for k in kwargs if k.startswith(RepositoryField.PREFIX)]:
                 k = RepositoryField.un_prefix_key(field)
                 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
                 if ex_field:
@@ -339,8 +337,8 @@
         fork_of = Repository.guess_instance(fork_of)
         repo_group = RepoGroup.guess_instance(repo_group)
         try:
-            repo_name = safe_unicode(repo_name)
-            description = safe_unicode(description)
+            repo_name = repo_name
+            description = description
             # repo name is just a name of repository
             # while repo_name_full is a full qualified name that is combined
             # with name and path of group
@@ -360,7 +358,7 @@
             new_repo.private = private
             if clone_uri:
                 # will raise exception on error
-                is_valid_repo_uri(repo_type, clone_uri, make_ui(clear_session=False))
+                is_valid_repo_uri(repo_type, clone_uri, make_ui())
             new_repo.clone_uri = clone_uri
             new_repo.landing_rev = landing_rev
 
@@ -643,8 +641,7 @@
             _paths = [repo_store_location]
         else:
             _paths = [self.repos_path, new_parent_path, repo_name]
-            # we need to make it str for mercurial
-        repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
+        repo_path = os.path.join(*_paths)
 
         # check if this path is not a repository
         if is_valid_repo(repo_path, self.repos_path):
@@ -655,13 +652,13 @@
             raise Exception('This path %s is a valid group' % repo_path)
 
         log.info('creating repo %s in %s from url: `%s`',
-            repo_name, safe_unicode(repo_path),
+            repo_name, repo_path,
             obfuscate_url_pw(clone_uri))
 
         backend = get_backend(repo_type)
 
         if repo_type == 'hg':
-            baseui = make_ui(clear_session=False)
+            baseui = make_ui()
             # patch and reset hooks section of UI config to not run any
             # hooks on creating remote repo
             for k, v in baseui.configitems('hooks'):
@@ -676,7 +673,7 @@
             raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
 
         log.debug('Created repo %s with %s backend',
-                  safe_unicode(repo_name), safe_unicode(repo_type))
+                  repo_name, repo_type)
         return repo
 
     def _rename_filesystem_repo(self, old, new):
@@ -688,8 +685,8 @@
         """
         log.info('renaming repo from %s to %s', old, new)
 
-        old_path = safe_str(os.path.join(self.repos_path, old))
-        new_path = safe_str(os.path.join(self.repos_path, new))
+        old_path = os.path.join(self.repos_path, old)
+        new_path = os.path.join(self.repos_path, new)
         if os.path.isdir(new_path):
             raise Exception(
                 'Was trying to rename to already existing dir %s' % new_path
@@ -704,7 +701,7 @@
 
         :param repo: repo object
         """
-        rm_path = safe_str(os.path.join(self.repos_path, repo.repo_name))
+        rm_path = os.path.join(self.repos_path, repo.repo_name)
         log.info("Removing %s", rm_path)
 
         _now = datetime.now()
@@ -715,6 +712,6 @@
             args = repo.group.full_path_splitted + [_d]
             _d = os.path.join(*args)
         if os.path.exists(rm_path):
-            shutil.move(rm_path, safe_str(os.path.join(self.repos_path, _d)))
+            shutil.move(rm_path, os.path.join(self.repos_path, _d))
         else:
             log.error("Can't find repo to delete in %r", rm_path)
--- a/kallithea/model/scm.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/scm.py	Thu Feb 06 01:19:23 2020 +0100
@@ -25,7 +25,6 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-import cStringIO
 import logging
 import os
 import posixpath
@@ -42,7 +41,7 @@
 from kallithea.lib.exceptions import IMCCommitError, NonRelativePathError
 from kallithea.lib.hooks import process_pushed_raw_ids
 from kallithea.lib.utils import action_logger, get_filesystem_repos, make_ui
-from kallithea.lib.utils2 import safe_str, safe_unicode, set_hook_environment
+from kallithea.lib.utils2 import safe_bytes, set_hook_environment
 from kallithea.lib.vcs import get_backend
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.exceptions import RepositoryError
@@ -140,9 +139,11 @@
         cls = Repository
         if isinstance(instance, cls):
             return instance
-        elif isinstance(instance, int) or safe_str(instance).isdigit():
+        elif isinstance(instance, int):
             return cls.get(instance)
-        elif isinstance(instance, basestring):
+        elif isinstance(instance, str):
+            if instance.isdigit():
+                return cls.get(int(instance))
             return cls.get_by_repo_name(instance)
         elif instance is not None:
             raise Exception('given object must be int, basestr or Instance'
@@ -161,7 +162,8 @@
     def repo_scan(self, repos_path=None):
         """
         Listing of repositories in given path. This path should not be a
-        repository itself. Return a dictionary of repository objects
+        repository itself. Return a dictionary of repository objects mapping to
+        vcs instances.
 
         :param repos_path: path to directory containing repositories
         """
@@ -187,10 +189,10 @@
 
                     klass = get_backend(path[0])
 
-                    if path[0] == 'hg' and path[0] in BACKENDS.keys():
-                        repos[name] = klass(safe_str(path[1]), baseui=baseui)
+                    if path[0] == 'hg' and path[0] in BACKENDS:
+                        repos[name] = klass(path[1], baseui=baseui)
 
-                    if path[0] == 'git' and path[0] in BACKENDS.keys():
+                    if path[0] == 'git' and path[0] in BACKENDS:
                         repos[name] = klass(path[1])
             except OSError:
                 continue
@@ -394,17 +396,8 @@
         """
         user = User.guess_instance(user)
         IMC = self._get_IMC_module(repo.alias)
-
-        # decoding here will force that we have proper encoded values
-        # in any other case this will throw exceptions and deny commit
-        content = safe_str(content)
-        path = safe_str(f_path)
-        # message and author needs to be unicode
-        # proper backend should then translate that into required type
-        message = safe_unicode(message)
-        author = safe_unicode(author)
         imc = IMC(repo)
-        imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
+        imc.change(FileNode(f_path, content, mode=cs.get_file_mode(f_path)))
         try:
             tip = imc.commit(message=message, author=author,
                              parents=[cs], branch=cs.branch)
@@ -480,22 +473,14 @@
         for f_path in nodes:
             content = nodes[f_path]['content']
             f_path = self._sanitize_path(f_path)
-            f_path = safe_str(f_path)
-            # decoding here will force that we have proper encoded values
-            # in any other case this will throw exceptions and deny commit
-            if isinstance(content, (basestring,)):
-                content = safe_str(content)
-            elif isinstance(content, (file, cStringIO.OutputType,)):
+            if not isinstance(content, str) and not isinstance(content, bytes):
                 content = content.read()
-            else:
-                raise Exception('Content is of unrecognized type %s' % (
-                    type(content)
-                ))
             processed_nodes.append((f_path, content))
 
-        message = safe_unicode(message)
+        message = message
         committer = user.full_contact
-        author = safe_unicode(author) if author else committer
+        if not author:
+            author = committer
 
         IMC = self._get_IMC_module(scm_instance.alias)
         imc = IMC(scm_instance)
@@ -536,9 +521,10 @@
         user = User.guess_instance(user)
         scm_instance = repo.scm_instance_no_cache()
 
-        message = safe_unicode(message)
+        message = message
         committer = user.full_contact
-        author = safe_unicode(author) if author else committer
+        if not author:
+            author = committer
 
         imc_class = self._get_IMC_module(scm_instance.alias)
         imc = imc_class(scm_instance)
@@ -616,9 +602,10 @@
             content = nodes[f_path].get('content')
             processed_nodes.append((f_path, content))
 
-        message = safe_unicode(message)
+        message = message
         committer = user.full_contact
-        author = safe_unicode(author) if author else committer
+        if not author:
+            author = committer
 
         IMC = self._get_IMC_module(scm_instance.alias)
         imc = IMC(scm_instance)
@@ -673,18 +660,18 @@
         repo = repo.scm_instance
 
         branches_group = ([(u'branch:%s' % k, k) for k, v in
-                           repo.branches.iteritems()], _("Branches"))
+                           repo.branches.items()], _("Branches"))
         hist_l.append(branches_group)
         choices.extend([x[0] for x in branches_group[0]])
 
         if repo.alias == 'hg':
             bookmarks_group = ([(u'book:%s' % k, k) for k, v in
-                                repo.bookmarks.iteritems()], _("Bookmarks"))
+                                repo.bookmarks.items()], _("Bookmarks"))
             hist_l.append(bookmarks_group)
             choices.extend([x[0] for x in bookmarks_group[0]])
 
         tags_group = ([(u'tag:%s' % k, k) for k, v in
-                       repo.tags.iteritems()], _("Tags"))
+                       repo.tags.items()], _("Tags"))
         hist_l.append(tags_group)
         choices.extend([x[0] for x in tags_group[0]])
 
@@ -702,7 +689,7 @@
         # FIXME This may not work on Windows and may need a shell wrapper script.
         return (kallithea.CONFIG.get('git_hook_interpreter')
                 or sys.executable
-                or '/usr/bin/env python2')
+                or '/usr/bin/env python3')
 
     def install_git_hooks(self, repo, force_create=False):
         """
@@ -718,11 +705,11 @@
         if not os.path.isdir(loc):
             os.makedirs(loc)
 
-        tmpl_post = "#!%s\n" % self._get_git_hook_interpreter()
+        tmpl_post = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
         tmpl_post += pkg_resources.resource_string(
             'kallithea', os.path.join('config', 'post_receive_tmpl.py')
         )
-        tmpl_pre = "#!%s\n" % self._get_git_hook_interpreter()
+        tmpl_pre = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
         tmpl_pre += pkg_resources.resource_string(
             'kallithea', os.path.join('config', 'pre_receive_tmpl.py')
         )
@@ -736,12 +723,11 @@
                 log.debug('hook exists, checking if it is from kallithea')
                 with open(_hook_file, 'rb') as f:
                     data = f.read()
-                    matches = re.compile(r'(?:%s)\s*=\s*(.*)'
-                                         % 'KALLITHEA_HOOK_VER').search(data)
+                    matches = re.search(br'^KALLITHEA_HOOK_VER\s*=\s*(.*)$', data, flags=re.MULTILINE)
                     if matches:
                         try:
                             ver = matches.groups()[0]
-                            log.debug('got %s it is kallithea', ver)
+                            log.debug('Found Kallithea hook - it has KALLITHEA_HOOK_VER %r', ver)
                             has_hook = True
                         except Exception:
                             log.error(traceback.format_exc())
@@ -753,9 +739,9 @@
                 log.debug('writing %s hook file !', h_type)
                 try:
                     with open(_hook_file, 'wb') as f:
-                        tmpl = tmpl.replace('_TMPL_', kallithea.__version__)
+                        tmpl = tmpl.replace(b'_TMPL_', safe_bytes(kallithea.__version__))
                         f.write(tmpl)
-                    os.chmod(_hook_file, 0755)
+                    os.chmod(_hook_file, 0o755)
                 except IOError as e:
                     log.error('error writing %s: %s', _hook_file, e)
             else:
--- a/kallithea/model/ssh_key.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/ssh_key.py	Thu Feb 06 01:19:23 2020 +0100
@@ -29,7 +29,8 @@
 from tg.i18n import ugettext as _
 
 from kallithea.lib import ssh
-from kallithea.lib.utils2 import safe_str, str2bool
+from kallithea.lib.utils2 import str2bool
+from kallithea.lib.vcs.exceptions import RepositoryError
 from kallithea.model.db import User, UserSshKeys
 from kallithea.model.meta import Session
 
@@ -37,7 +38,7 @@
 log = logging.getLogger(__name__)
 
 
-class SshKeyModelException(Exception):
+class SshKeyModelException(RepositoryError):
     """Exception raised by SshKeyModel methods to report errors"""
 
 
@@ -53,7 +54,7 @@
         try:
             keytype, pub, comment = ssh.parse_pub_key(public_key)
         except ssh.SshKeyParseError as e:
-            raise SshKeyModelException(_('SSH key %r is invalid: %s') % (safe_str(public_key), e.message))
+            raise SshKeyModelException(_('SSH key %r is invalid: %s') % (public_key, e.args[0]))
         if not description.strip():
             description = comment.strip()
 
@@ -72,21 +73,19 @@
 
         return new_ssh_key
 
-    def delete(self, public_key, user=None):
+    def delete(self, fingerprint, user):
         """
-        Deletes given public_key, if user is set it also filters the object for
-        deletion by given user.
+        Deletes ssh key with given fingerprint for the given user.
         Will raise SshKeyModelException on errors
         """
-        ssh_key = UserSshKeys.query().filter(UserSshKeys._public_key == public_key)
+        ssh_key = UserSshKeys.query().filter(UserSshKeys.fingerprint == fingerprint)
 
-        if user:
-            user = User.guess_instance(user)
-            ssh_key = ssh_key.filter(UserSshKeys.user_id == user.user_id)
+        user = User.guess_instance(user)
+        ssh_key = ssh_key.filter(UserSshKeys.user_id == user.user_id)
 
         ssh_key = ssh_key.scalar()
         if ssh_key is None:
-            raise SshKeyModelException(_('SSH key %r not found') % safe_str(public_key))
+            raise SshKeyModelException(_('SSH key with fingerprint %r found') % fingerprint)
         Session().delete(ssh_key)
 
     def get_ssh_keys(self, user):
@@ -116,7 +115,7 @@
         # Now, test that the directory is or was created in a readable way by previous.
         if not (os.path.isdir(authorized_keys_dir) and
                 os.access(authorized_keys_dir, os.W_OK)):
-            raise Exception("Directory of authorized_keys cannot be written to so authorized_keys file %s cannot be written" % (authorized_keys))
+            raise SshKeyModelException("Directory of authorized_keys cannot be written to so authorized_keys file %s cannot be written" % (authorized_keys))
 
         # Make sure we don't overwrite a key file with important content
         if os.path.exists(authorized_keys):
@@ -127,10 +126,11 @@
                     elif ssh.SSH_OPTIONS in l and ' ssh-serve ' in l:
                         pass # Kallithea entries are ok to overwrite
                     else:
-                        raise Exception("Safety check failed, found %r in %s - please review and remove it" % (l.strip(), authorized_keys))
+                        raise SshKeyModelException("Safety check failed, found %r line in %s - please remove it if Kallithea should manage the file" % (l.strip(), authorized_keys))
 
         fh, tmp_authorized_keys = tempfile.mkstemp('.authorized_keys', dir=os.path.dirname(authorized_keys))
         with os.fdopen(fh, 'w') as f:
+            f.write("# WARNING: This .ssh/authorized_keys file is managed by Kallithea. Manual editing or adding new entries will make Kallithea back off.\n")
             for key in UserSshKeys.query().join(UserSshKeys.user).filter(User.active == True):
                 f.write(ssh.authorized_keys_line(kallithea_cli_path, config['__file__'], key))
         os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
--- a/kallithea/model/user.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/user.py	Thu Feb 06 01:19:23 2020 +0100
@@ -38,7 +38,7 @@
 
 from kallithea.lib.caching_query import FromCache
 from kallithea.lib.exceptions import DefaultUserException, UserOwnsReposException
-from kallithea.lib.utils2 import generate_api_key, get_current_authuser, safe_unicode
+from kallithea.lib.utils2 import generate_api_key, get_current_authuser
 from kallithea.model.db import Permission, User, UserEmailMap, UserIpMap, UserToPerm
 from kallithea.model.meta import Session
 
@@ -142,10 +142,8 @@
             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 = extern_name
+            new_user.extern_type = extern_type
             new_user.name = firstname
             new_user.lastname = lastname
 
--- a/kallithea/model/user_group.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/user_group.py	Thu Feb 06 01:19:23 2020 +0100
@@ -126,7 +126,7 @@
                 if k == 'users_group_members':
                     members_list = []
                     if v:
-                        v = [v] if isinstance(v, basestring) else v
+                        v = [v] if isinstance(v, str) else v
                         for u_id in set(v):
                             member = UserGroupMember(user_group.users_group_id, u_id)
                             members_list.append(member)
--- a/kallithea/model/validators.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/model/validators.py	Thu Feb 06 01:19:23 2020 +0100
@@ -235,7 +235,7 @@
 
         def _validate_python(self, value, state):
             try:
-                (value or '').decode('ascii')
+                (value or '').encode('ascii')
             except UnicodeError:
                 msg = self.message('invalid_password', state)
                 raise formencode.Invalid(msg, value, state,)
@@ -412,7 +412,7 @@
 
             if url and url != value.get('clone_uri_hidden'):
                 try:
-                    is_valid_repo_uri(repo_type, url, make_ui(clear_session=False))
+                    is_valid_repo_uri(repo_type, url, make_ui())
                 except Exception:
                     log.exception('URL validation failed')
                     msg = self.message('clone_uri', state)
@@ -544,7 +544,7 @@
 
             # CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
             new_perms_group = defaultdict(dict)
-            for k, v in value.copy().iteritems():
+            for k, v in value.copy().items():
                 if k.startswith('perm_new_member'):
                     del value[k]
                     _type, part = k.split('perm_new_member_')
@@ -556,15 +556,15 @@
                         new_perms_group[pos][_key] = v
 
             # fill new permissions in order of how they were added
-            for k in sorted(map(int, new_perms_group.keys())):
-                perm_dict = new_perms_group[str(k)]
+            for k in sorted(new_perms_group, key=lambda k: int(k)):
+                perm_dict = new_perms_group[k]
                 new_member = perm_dict.get('name')
                 new_perm = perm_dict.get('perm')
                 new_type = perm_dict.get('type')
                 if new_member and new_perm and new_type:
                     perms_new.add((new_member, new_perm, new_type))
 
-            for k, v in value.iteritems():
+            for k, v in value.items():
                 if k.startswith('u_perm_') or k.startswith('g_perm_'):
                     member = k[7:]
                     t = {'u': 'user',
@@ -782,7 +782,7 @@
 
         def _convert_to_python(self, value, state):
             # filter empty values
-            return filter(lambda s: s not in [None, ''], value)
+            return [s for s in value if s not in [None, '']]
 
         def _validate_python(self, value, state):
             from kallithea.lib import auth_modules
--- a/kallithea/public/js/base.js	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/public/js/base.js	Thu Feb 06 01:19:23 2020 +0100
@@ -697,7 +697,7 @@
             if (!confirm('Confirm to delete this pull request')) {
                 return false;
             }
-            var comments = $('.comment').size();
+            var comments = $('.comment').length;
             if (comments > 0 &&
                 !confirm('Confirm again to delete this pull request with {0} comments'.format(comments))) {
                 return false;
--- a/kallithea/templates/about.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/about.html	Thu Feb 06 01:19:23 2020 +0100
@@ -24,16 +24,27 @@
   necessarily limited to the following:</p>
   <ul>
 
-  <li>Copyright &copy; 2012&ndash;2019, Mads Kiilerich</li>
+  <li>Copyright &copy; 2012&ndash;2020, Mads Kiilerich</li>
+  <li>Copyright &copy; 2014&ndash;2020, Thomas De Schampheleire</li>
+  <li>Copyright &copy; 2020, Dennis Fink</li>
   <li>Copyright &copy; 2012, 2014&ndash;2017, 2019, Andrej Shadura</li>
-  <li>Copyright &copy; 2014&ndash;2019, Thomas De Schampheleire</li>
   <li>Copyright &copy; 2015&ndash;2017, 2019, Étienne Gilli</li>
   <li>Copyright &copy; 2017&ndash;2019, Allan Nordhøy</li>
   <li>Copyright &copy; 2018&ndash;2019, ssantos</li>
+  <li>Copyright &copy; 2019, Adi Kriegisch</li>
   <li>Copyright &copy; 2019, Danni Randeris</li>
   <li>Copyright &copy; 2019, Edmund Wong</li>
+  <li>Copyright &copy; 2019, Elizabeth Sherrock</li>
+  <li>Copyright &copy; 2019, Hüseyin Tunç</li>
+  <li>Copyright &copy; 2019, leela</li>
   <li>Copyright &copy; 2019, Manuel Jacob</li>
+  <li>Copyright &copy; 2019, Mateusz Mendel</li>
+  <li>Copyright &copy; 2019, Nathan</li>
+  <li>Copyright &copy; 2019, Oleksandr Shtalinberg</li>
+  <li>Copyright &copy; 2019, Private</li>
+  <li>Copyright &copy; 2019, THANOS SIOURDAKIS</li>
   <li>Copyright &copy; 2019, Wolfgang Scherer</li>
+  <li>Copyright &copy; 2019, Христо Станев</li>
   <li>Copyright &copy; 2012, 2014&ndash;2018, Dominik Ruf</li>
   <li>Copyright &copy; 2014&ndash;2015, 2018, Michal Čihař</li>
   <li>Copyright &copy; 2015, 2018, Branko Majic</li>
--- a/kallithea/templates/admin/gists/edit.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/admin/gists/edit.html	Thu Feb 06 01:19:23 2020 +0100
@@ -67,13 +67,13 @@
             % for cnt, file in enumerate(c.files):
                 <div id="body" class="panel panel-default form-inline">
                     <div class="panel-heading">
-                        <input type="hidden" value="${h.safe_unicode(file.path)}" name="org_files">
-                        <input class="form-control" id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${h.safe_unicode(file.path)}">
+                        <input type="hidden" value="${file.path}" name="org_files">
+                        <input class="form-control" id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${file.path}">
                         <select class="form-control" id="mimetype_${h.FID('f',file.path)}" name="mimetypes"></select>
                     </div>
                     <div class="panel-body no-padding">
                         <div id="editor_container">
-                            <textarea id="editor_${h.FID('f',file.path)}" name="contents" style="display:none">${file.content}</textarea>
+                            <textarea id="editor_${h.FID('f',file.path)}" name="contents" style="display:none">${safe_str(file.content)}</textarea>
                         </div>
                     </div>
                 </div>
--- a/kallithea/templates/admin/gists/index.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/admin/gists/index.html	Thu Feb 06 01:19:23 2020 +0100
@@ -61,7 +61,7 @@
             <div class="text-muted">${gist.gist_description}</div>
           </div>
         % endfor
-        ${c.gists_pager.pager(**request.GET.mixed())}
+        ${c.gists_pager.pager()}
       %else:
         <div>${_('There are no gists yet')}</div>
       %endif
--- a/kallithea/templates/admin/gists/show.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/admin/gists/show.html	Thu Feb 06 01:19:23 2020 +0100
@@ -76,10 +76,10 @@
               <div class="panel panel-default">
                 <div id="${h.FID('G', file.path)}" class="panel-heading clearfix">
                     <div class="pull-left">
-                      <b>${h.safe_unicode(file.path)}</b>
+                      <b>${file.path}</b>
                     </div>
                     <div class="pull-right">
-                      ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=h.safe_unicode(file.path)),class_="btn btn-default btn-xs")}
+                      ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=file.path),class_="btn btn-default btn-xs")}
                     </div>
                 </div>
                 <div class="panel-body no-padding">
--- a/kallithea/templates/admin/my_account/my_account_ssh_keys.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/admin/my_account/my_account_ssh_keys.html	Thu Feb 06 01:19:23 2020 +0100
@@ -23,7 +23,7 @@
             </td>
             <td>
                 ${h.form(url('my_account_ssh_keys_delete'))}
-                    ${h.hidden('del_public_key', ssh_key.public_key)}
+                    ${h.hidden('del_public_key_fingerprint', ssh_key.fingerprint)}
                     <button class="btn btn-danger btn-xs" type="submit"
                             onclick="return confirm('${_('Confirm to remove this SSH key: %s') % ssh_key.fingerprint}');">
                         <i class="icon-trashcan"></i>
--- a/kallithea/templates/admin/settings/settings_visual.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/admin/settings/settings_visual.html	Thu Feb 06 01:19:23 2020 +0100
@@ -58,6 +58,8 @@
                     ${h.text('clone_ssh_tmpl', size=80, class_='form-control')}
                     <span class="help-block">${_('''Schema for constructing SSH clone URL, eg. 'ssh://{system_user}@{hostname}/{repo}'.''')}</span>
                 </div>
+                %else:
+                ${h.hidden('clone_ssh_tmpl', size=80, class_='form-control')}
                 %endif
             </div>
 
--- a/kallithea/templates/admin/users/user_edit_ssh_keys.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/admin/users/user_edit_ssh_keys.html	Thu Feb 06 01:19:23 2020 +0100
@@ -23,7 +23,7 @@
             </td>
             <td>
                 ${h.form(url('edit_user_ssh_keys_delete', id=c.user.user_id))}
-                    ${h.hidden('del_public_key', ssh_key.public_key)}
+                    ${h.hidden('del_public_key_fingerprint', ssh_key.fingerprint)}
                     <button class="btn btn-danger btn-xs" type="submit"
                             onclick="return confirm('${_('Confirm to remove this SSH key: %s') % ssh_key.fingerprint}');">
                         <i class="icon-trashcan"></i>
--- a/kallithea/templates/base/base.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/base/base.html	Thu Feb 06 01:19:23 2020 +0100
@@ -23,7 +23,7 @@
             <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
         %endif
         which is
-        <a class="navbar-link" href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2019 by various authors &amp; licensed under GPLv3</a>.
+        <a class="navbar-link" href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2020 by various authors &amp; licensed under GPLv3</a>.
         %if c.issues_url:
             &ndash; <a class="navbar-link" href="${c.issues_url}" target="_blank">${_('Support')}</a>
         %endif
--- a/kallithea/templates/base/flash_msg.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/base/flash_msg.html	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
         % for message in messages:
             <div class="alert alert-dismissable ${alert_categories[message.category]}" role="alert">
               <button type="button" class="close" data-dismiss="alert" aria-hidden="true"><i class="icon-cancel-circled"></i></button>
-              ${message}
+              ${message.message|n}
             </div>
         % endfor
     % endif
--- a/kallithea/templates/base/perms_summary.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/base/perms_summary.html	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 
 <%def name="perms_summary(permissions, show_all=False, actions=True)">
 <div id="perms">
-     %for section in sorted(permissions.keys()):
+     %for section in sorted(permissions):
         <div class="perms_section_head">
             <h4>${section.replace("_"," ").capitalize()}</h4>
             %if section != 'global':
--- a/kallithea/templates/changeset/changeset.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/changeset/changeset.html	Thu Feb 06 01:19:23 2020 +0100
@@ -90,16 +90,14 @@
                          <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') %>
-                     %if rev:
+                     %if c.changeset_graft_source_hash:
                      <div>
-                       ${_('Grafted from:')} ${h.link_to(h.short_id(rev),h.url('changeset_home',repo_name=c.repo_name,revision=rev), class_="changeset_hash")}
+                       ${_('Grafted from:')} ${h.link_to(h.short_id(c.changeset_graft_source_hash),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset_graft_source_hash), class_="changeset_hash")}
                      </div>
                      %endif
-                     <% rev = c.changeset.extra.get('transplant_source', '').encode('hex') %>
-                     %if rev:
+                     %if c.changeset_transplant_source_hash:
                      <div>
-                       ${_('Transplanted from:')} ${h.link_to(h.short_id(rev),h.url('changeset_home',repo_name=c.repo_name,revision=rev), class_="changeset_hash")}
+                       ${_('Transplanted from:')} ${h.link_to(h.short_id(c.changeset_transplant_source_hash),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset_transplant_source_hash), class_="changeset_hash")}
                      </div>
                      %endif
 
@@ -145,7 +143,7 @@
                 %for fid, url_fid, op, a_path, path, diff, stats in file_diff_data:
                     <div class="cs_${op} clearfix">
                       <span class="node">
-                          <i class="icon-diff-${op}"></i>${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                          <i class="icon-diff-${op}"></i>${h.link_to(path, '#%s' % fid)}
                       </span>
                       <div class="changes">${h.fancy_file_stats(stats)}</div>
                     </div>
--- a/kallithea/templates/changeset/changeset_file_comment.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/changeset/changeset_file_comment.html	Thu Feb 06 01:19:23 2020 +0100
@@ -163,8 +163,8 @@
 ## original location of comments ... but the ones outside diff context remains here
 <div class="comments inline-comments">
   %for f_path, lines in c.inline_comments:
-    %for line_no, comments in lines.iteritems():
-      <div class="comments-list-chunk" data-f_path="${f_path}" data-line_no="${line_no}" data-target-id="${h.safeid(h.safe_unicode(f_path))}_${line_no}">
+    %for line_no, comments in lines.items():
+      <div class="comments-list-chunk" data-f_path="${f_path}" data-line_no="${line_no}" data-target-id="${h.safeid(f_path)}_${line_no}">
         %for co in comments:
             ${comment_block(co)}
         %endfor
@@ -198,7 +198,7 @@
 
    $(window).on('beforeunload', function(){
       var $textareas = $('.comment-inline-form textarea[name=text]');
-      if($textareas.size() > 1 ||
+      if($textareas.length > 1 ||
          $textareas.val()) {
          // this message will not be displayed on all browsers
          // (e.g. some versions of Firefox), but the user will still be warned
--- a/kallithea/templates/changeset/changeset_range.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/changeset/changeset_range.html	Thu Feb 06 01:19:23 2020 +0100
@@ -56,7 +56,7 @@
                             <div class="cs_${op} clearfix">
                                 <span class="node">
                                     <i class="icon-diff-${op}"></i>
-                                    ${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                                    ${h.link_to(path, '#%s' % fid)}
                                 </span>
                                 <div class="changes">${h.fancy_file_stats(stats)}</div>
                             </div>
--- a/kallithea/templates/changeset/diff_block.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/changeset/diff_block.html	Thu Feb 06 01:19:23 2020 +0100
@@ -22,7 +22,7 @@
     <div id="${id_fid}" class="panel panel-default ${cls}">
         <div class="panel-heading clearfix">
                 <div class="pull-left">
-                    ${h.safe_unicode(cs_filename)}
+                    ${cs_filename}
                 </div>
                 <div class="pull-left diff-actions">
                   <span>
@@ -57,13 +57,13 @@
                     %endif
                   </span>
 
-                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full diff for this file')}">
+                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full diff for this file')}">
                       <i class="icon-file-code"></i></a>
-                  <a href="${h.url('files_diff_2way_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full side-by-side diff for this file')}">
+                  <a href="${h.url('files_diff_2way_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full side-by-side diff for this file')}">
                       <i class="icon-docs"></i></a>
-                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='raw')}" data-toggle="tooltip" title="${_('Raw diff')}">
+                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='raw')}" data-toggle="tooltip" title="${_('Raw diff')}">
                       <i class="icon-diff"></i></a>
-                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='download')}" data-toggle="tooltip" title="${_('Download diff')}">
+                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='download')}" data-toggle="tooltip" title="${_('Download diff')}">
                       <i class="icon-floppy"></i></a>
                   ${c.ignorews_url(request.GET, url_fid)}
                   ${c.context_url(request.GET, url_fid)}
@@ -73,7 +73,7 @@
                     ${h.checkbox('checkbox-show-inline-' + id_fid, checked="checked",class_="show-inline-comments",**{'data-id_for':id_fid})}
                 </div>
         </div>
-        <div class="no-padding panel-body" data-f_path="${h.safe_unicode(cs_filename)}">
+        <div class="no-padding panel-body" data-f_path="${cs_filename}">
             ${diff|n}
             %if op and cs_filename.rsplit('.')[-1] in ['png', 'gif', 'jpg', 'bmp']:
               <div class="btn btn-image-diff-show">Show images</div>
--- a/kallithea/templates/compare/compare_diff.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/compare/compare_diff.html	Thu Feb 06 01:19:23 2020 +0100
@@ -72,7 +72,7 @@
                     <div class="cs_${op} clearfix">
                       <span class="node">
                           <i class="icon-diff-${op}"></i>
-                          ${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                          ${h.link_to(path, '#%s' % fid)}
                       </span>
                       <div class="changes">${h.fancy_file_stats(stats)}</div>
                     </div>
--- a/kallithea/templates/files/diff_2way.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/files/diff_2way.html	Thu Feb 06 01:19:23 2020 +0100
@@ -34,22 +34,22 @@
         <div class="panel panel-default">
             <div class="panel-heading clearfix">
                     <div class="pull-left">
-                        ${h.link_to(h.safe_unicode(c.node1.path),h.url('files_home',repo_name=c.repo_name,
-                        revision=c.cs2.raw_id,f_path=h.safe_unicode(c.node1.path)))}
+                        ${h.link_to(c.node1.path,h.url('files_home',repo_name=c.repo_name,
+                        revision=c.cs2.raw_id,f_path=c.node1.path))}
                     </div>
                     <div class="pull-left diff-actions">
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}"
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=c.node1.path,diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}"
                          data-toggle="tooltip"
                          title="${_('Show full diff for this file')}">
                           <i class="icon-file-code"></i></a>
-                      <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}"
+                      <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=c.node1.path,diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}"
                          data-toggle="tooltip"
                          title="${_('Show full side-by-side diff for this file')}">
                           <i class="icon-docs"></i></a>
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='raw')}"
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=c.node1.path,diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='raw')}"
                          data-toggle="tooltip"
                          title="${_('Raw diff')}"><i class="icon-diff"></i></a>
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='download')}"
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=c.node1.path,diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='download')}"
                          data-toggle="tooltip"
                          title="${_('Download diff')}"><i class="icon-floppy"></i></a>
                       ${h.checkbox('ignorews', label=_('Ignore whitespace'))}
@@ -61,13 +61,17 @@
     </div>
 
 <script>
-var orig1_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.cs1.raw_id))};
-var orig2_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.cs2.raw_id))};
+var orig1_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=c.node1.path,revision=c.cs1.raw_id))};
+var orig2_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=c.node2.path,revision=c.cs2.raw_id))};
 
 $(document).ready(function () {
     $('#compare').mergely({
         width: 'auto',
-        height: $(window).height() - $('#compare').offset().top - $('#footer').height() - 35,
+        /* let the 2 compare panes use all the window space currently available
+           below footer (i.e. on an empty page with just header and footer): */
+        height: $(window).height()
+                - $('.footer').offset().top
+                - $('.footer').height(),
         fgcolor: {a:'#ddffdd',c:'#cccccc',d:'#ffdddd'},
         bgcolor: '#fff',
         viewport: true,
--- a/kallithea/templates/files/files.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/files/files.html	Thu Feb 06 01:19:23 2020 +0100
@@ -3,7 +3,7 @@
 <%block name="title">
     ${_('%s Files') % c.repo_name}
     %if hasattr(c,'file'):
-        &middot; ${h.safe_unicode(c.file.path) or '/'}
+        &middot; ${c.file.path or '/'}
     %endif
 </%block>
 
@@ -217,12 +217,12 @@
     });
 
     // init the search filter
-    var _node_list_url = node_list_url.replace('__REV__', ${h.js(c.changeset.raw_id)}).replace('__FPATH__', ${h.js(h.safe_unicode(c.file.path))});
+    var _node_list_url = node_list_url.replace('__REV__', ${h.js(c.changeset.raw_id)}).replace('__FPATH__', ${h.js(c.file.path)});
     var _url_base = url_base.replace('__REV__', ${h.js(c.changeset.raw_id)});
     fileBrowserListeners(_node_list_url, _url_base);
 
     var initial_state = {url:window.location.href, title:document.title, url_base:_url_base,
-         node_list_url:_node_list_url, rev:${h.js(c.changeset.raw_id)}, f_path:${h.js(h.safe_unicode(c.file.path))}};
+         node_list_url:_node_list_url, rev:${h.js(c.changeset.raw_id)}, f_path:${h.js(c.file.path)}};
 
     // change branch filter
     $("#branch_selector").select2({
@@ -234,7 +234,7 @@
     $("#branch_selector").change(function(e){
         var selected = e.currentTarget.options[e.currentTarget.selectedIndex].value;
         if(selected && selected != ${h.js(c.changeset.raw_id)}){
-            window.location = pyroutes.url('files_home', {'repo_name': ${h.js(h.safe_unicode(c.repo_name))}, 'revision': selected, 'f_path': ${h.js(h.safe_unicode(c.file.path))}});
+            window.location = pyroutes.url('files_home', {'repo_name': ${h.js(c.repo_name)}, 'revision': selected, 'f_path': ${h.js(c.file.path)}});
             $("#body").hide();
         } else {
             $("#branch_selector").val(${h.js(c.changeset.raw_id)});
@@ -242,7 +242,7 @@
     });
     $('#show_authors').on('click', function(){
         $.ajax({
-            url: pyroutes.url('files_authors_home', {'revision': ${h.js(c.changeset.raw_id)}, 'f_path': ${h.js(h.safe_unicode(c.file.path))}}),
+            url: pyroutes.url('files_authors_home', {'revision': ${h.js(c.changeset.raw_id)}, 'f_path': ${h.js(c.file.path)}}),
             success: function(data) {
                 $('#file_authors').html(data);
                 $('#file_authors').show();
--- a/kallithea/templates/files/files_browser.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/files/files_browser.html	Thu Feb 06 01:19:23 2020 +0100
@@ -13,7 +13,7 @@
     %if node.is_submodule():
         <%return node.url or '#'%>
     %else:
-        <%return h.url('files_home', repo_name=c.repo_name, revision=c.changeset.raw_id, f_path=h.safe_unicode(node.path))%>
+        <%return h.url('files_home', repo_name=c.repo_name, revision=c.changeset.raw_id, f_path=node.path)%>
     %endif
 </%def>
 <%def name="_file_name(iconclass, name)">
--- a/kallithea/templates/files/files_edit.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/files/files_edit.html	Thu Feb 06 01:19:23 2020 +0100
@@ -59,7 +59,7 @@
                     </span>
               </div>
               <div class="panel-body no-padding">
-                <textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
+                <textarea id="editor" name="content" style="display:none">${h.escape(h.safe_str(c.file.content))|n}</textarea>
               </div>
             </div>
             <div>
--- a/kallithea/templates/password_reset_confirmation.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/password_reset_confirmation.html	Thu Feb 06 01:19:23 2020 +0100
@@ -22,13 +22,13 @@
         ${h.form(h.url('reset_password_confirmation'), method='post')}
         <p>${_('You are about to set a new password for the email address %s.') % c.email}</p>
         <p>${_('Note that you must use the same browser session for this as the one used to request the password reset.')}</p>
-        ${h.hidden('email')}
-        ${h.hidden('timestamp')}
+        ${h.hidden('email', value=c.email)}
+        ${h.hidden('timestamp', value=c.timestamp)}
         <div class="form">
                 <div class="form-group">
                     <label class="control-label" for="token">${_('Code you received in the email')}:</label>
                     <div>
-                        ${h.text('token', class_='form-control')}
+                        ${h.text('token', value=c.token, class_='form-control')}
                     </div>
                 </div>
 
--- a/kallithea/templates/pullrequests/pullrequest_data.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_data.html	Thu Feb 06 01:19:23 2020 +0100
@@ -80,7 +80,7 @@
 </div>
 
 %if hasattr(pullrequests, 'pager'):
-    ${pullrequests.pager(**request.GET.mixed())}
+    ${pullrequests.pager()}
 %endif
 
 </%def>
--- a/kallithea/templates/pullrequests/pullrequest_show.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_show.html	Thu Feb 06 01:19:23 2020 +0100
@@ -300,7 +300,7 @@
                     <div class="cs_${op} clearfix">
                       <span class="node">
                           <i class="icon-diff-${op}"></i>
-                          ${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                          ${h.link_to(path, '#%s' % fid)}
                       </span>
                       <div class="changes">${h.fancy_file_stats(stats)}</div>
                     </div>
--- a/kallithea/templates/summary/statistics.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/summary/statistics.html	Thu Feb 06 01:19:23 2020 +0100
@@ -28,8 +28,8 @@
     </div>
 
     <div class="graph panel-body">
-         <div>
-         %if c.no_data:
+        <div>
+        %if not c.stats_percentage:
            ${c.no_data_msg}
            %if h.HasPermissionAny('hg.admin')('enable stats on from summary'):
                 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="btn btn-default btn-xs")}
@@ -54,7 +54,6 @@
 <script type="text/javascript">
 var data = ${h.js(c.trending_languages)};
 var total = 0;
-var no_data = true;
 var tbl = document.createElement('table');
 tbl.setAttribute('class','trending_language_tbl');
 var cnt = 0;
@@ -63,7 +62,6 @@
 }
 for (var i=0;i<data.length;i++){
     cnt += 1;
-    no_data = false;
 
     var hide = cnt>2;
     var tr = document.createElement('tr');
--- a/kallithea/templates/summary/summary.html	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/templates/summary/summary.html	Thu Feb 06 01:19:23 2020 +0100
@@ -338,7 +338,6 @@
 $(document).ready(function(){
     var data = ${h.js(c.trending_languages)};
     var total = 0;
-    var no_data = true;
     var tbl = document.createElement('table');
     tbl.setAttribute('class','table');
     var cnt = 0;
@@ -347,7 +346,6 @@
     }
     for (var i=0;i<data.length;i++){
         cnt += 1;
-        no_data = false;
 
         var hide = cnt>2;
         var tr = document.createElement('tr');
--- a/kallithea/tests/__init__.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/__init__.py	Thu Feb 06 01:19:23 2020 +0100
@@ -17,13 +17,3 @@
 
 Refer to docs/contributing.rst for details on running the test suite.
 """
-
-import pytest
-
-
-if getattr(pytest, 'register_assert_rewrite', None):
-    # make sure that all asserts under kallithea/tests benefit from advanced
-    # assert reporting with pytest-3.0.0+, including api/api_base.py,
-    # models/common.py etc.
-    # See also: https://docs.pytest.org/en/latest/assert.html#advanced-assertion-introspection
-    pytest.register_assert_rewrite('kallithea.tests')
--- a/kallithea/tests/api/api_base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/api/api_base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -23,8 +23,9 @@
 import mock
 import pytest
 
+from kallithea.lib import ext_json
 from kallithea.lib.auth import AuthUser
-from kallithea.lib.compat import json
+from kallithea.lib.utils2 import ascii_bytes
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.db import ChangesetStatus, PullRequest, RepoGroup, Repository, Setting, Ui, User
 from kallithea.model.gist import GistModel
@@ -34,7 +35,7 @@
 from kallithea.model.scm import ScmModel
 from kallithea.model.user import UserModel
 from kallithea.model.user_group import UserGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -48,11 +49,10 @@
 def _build_data(apikey, method, **kw):
     """
     Builds API data with given random ID
-
-    :param random_id:
+    For convenience, the json is returned as str
     """
     random_id = random.randrange(1, 9999)
-    return random_id, json.dumps({
+    return random_id, ext_json.dumps({
         "id": random_id,
         "api_key": apikey,
         "method": method,
@@ -60,7 +60,7 @@
     })
 
 
-jsonify = lambda obj: json.loads(json.dumps(obj))
+jsonify = lambda obj: ext_json.loads(ext_json.dumps(obj))
 
 
 def crash(*args, **kwargs):
@@ -75,15 +75,15 @@
 
 ## helpers
 def make_user_group(name=TEST_USER_GROUP):
-    gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
+    gr = fixture.create_user_group(name, cur_user=base.TEST_USER_ADMIN_LOGIN)
     UserGroupModel().add_user_to_group(user_group=gr,
-                                       user=TEST_USER_ADMIN_LOGIN)
+                                       user=base.TEST_USER_ADMIN_LOGIN)
     Session().commit()
     return gr
 
 
 def make_repo_group(name=TEST_REPO_GROUP):
-    gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
+    gr = fixture.create_repo_group(name, cur_user=base.TEST_USER_ADMIN_LOGIN)
     Session().commit()
     return gr
 
@@ -94,7 +94,7 @@
 
     @classmethod
     def setup_class(cls):
-        cls.usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        cls.usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         cls.apikey = cls.usr.api_key
         cls.test_user = UserModel().create_or_update(
             username='test-api',
@@ -127,7 +127,7 @@
             'error': None,
             'result': expected
         })
-        given = json.loads(given)
+        given = ext_json.loads(given)
         assert expected == given, (expected, given)
 
     def _compare_error(self, id_, expected, given):
@@ -136,7 +136,7 @@
             'error': expected,
             'result': None
         })
-        given = json.loads(given)
+        given = ext_json.loads(given)
         assert expected == given, (expected, given)
 
     def test_Optional_object(self):
@@ -230,10 +230,10 @@
 
     def test_api_get_user(self):
         id_, params = _build_data(self.apikey, 'get_user',
-                                  userid=TEST_USER_ADMIN_LOGIN)
+                                  userid=base.TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
 
-        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         ret = usr.get_api_data()
         ret['permissions'] = AuthUser(dbuser=usr).permissions
 
@@ -252,7 +252,7 @@
         id_, params = _build_data(self.apikey, 'get_user')
         response = api_call(self, params)
 
-        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         ret = usr.get_api_data()
         ret['permissions'] = AuthUser(dbuser=usr).permissions
 
@@ -394,22 +394,22 @@
 
     def test_api_create_existing_user(self):
         id_, params = _build_data(self.apikey, 'create_user',
-                                  username=TEST_USER_ADMIN_LOGIN,
+                                  username=base.TEST_USER_ADMIN_LOGIN,
                                   email='test@example.com',
                                   password='trololo')
         response = api_call(self, params)
 
-        expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
+        expected = "user `%s` already exist" % base.TEST_USER_ADMIN_LOGIN
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_create_user_with_existing_email(self):
         id_, params = _build_data(self.apikey, 'create_user',
-                                  username=TEST_USER_ADMIN_LOGIN + 'new',
-                                  email=TEST_USER_REGULAR_EMAIL,
+                                  username=base.TEST_USER_ADMIN_LOGIN + 'new',
+                                  email=base.TEST_USER_REGULAR_EMAIL,
                                   password='trololo')
         response = api_call(self, params)
 
-        expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
+        expected = "email `%s` already exist" % base.TEST_USER_REGULAR_EMAIL
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_create_user(self):
@@ -525,7 +525,7 @@
         expected = ret
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,expected', [
+    @base.parametrize('name,expected', [
         ('firstname', 'new_username'),
         ('lastname', 'new_username'),
         ('email', 'new_username'),
@@ -558,22 +558,22 @@
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_update_user_no_changed_params(self):
-        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         ret = jsonify(usr.get_api_data())
         id_, params = _build_data(self.apikey, 'update_user',
-                                  userid=TEST_USER_ADMIN_LOGIN)
+                                  userid=base.TEST_USER_ADMIN_LOGIN)
 
         response = api_call(self, params)
         ret = {
             'msg': 'updated user ID:%s %s' % (
-                usr.user_id, TEST_USER_ADMIN_LOGIN),
+                usr.user_id, base.TEST_USER_ADMIN_LOGIN),
             'user': ret
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_update_user_by_user_id(self):
-        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         ret = jsonify(usr.get_api_data())
         id_, params = _build_data(self.apikey, 'update_user',
                                   userid=usr.user_id)
@@ -581,7 +581,7 @@
         response = api_call(self, params)
         ret = {
             'msg': 'updated user ID:%s %s' % (
-                usr.user_id, TEST_USER_ADMIN_LOGIN),
+                usr.user_id, base.TEST_USER_ADMIN_LOGIN),
             'user': ret
         }
         expected = ret
@@ -598,7 +598,7 @@
 
     @mock.patch.object(UserModel, 'update_user', crash)
     def test_api_update_user_when_exception_happens(self):
-        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         ret = jsonify(usr.get_api_data())
         id_, params = _build_data(self.apikey, 'update_user',
                                   userid=usr.user_id)
@@ -658,7 +658,7 @@
         assert u"v0.2.0" in response.json[u'result'][u'tags']
         assert u'pull_requests' in response.json[u'result']
 
-    @parametrize('grant_perm', [
+    @base.parametrize('grant_perm', [
         ('repository.admin'),
         ('repository.write'),
         ('repository.read'),
@@ -755,7 +755,7 @@
 
         self._compare_ok(id_, expected, given=response.body)
 
-    @parametrize('name,ret_type', [
+    @base.parametrize('name,ret_type', [
         ('all', 'all'),
         ('dirs', 'dirs'),
         ('files', 'files'),
@@ -807,10 +807,10 @@
         response = api_call(self, params)
 
         expected = ('ret_type must be one of %s'
-                    % (','.join(['files', 'dirs', 'all'])))
+                    % (','.join(sorted(['files', 'dirs', 'all']))))
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,ret_type,grant_perm', [
+    @base.parametrize('name,ret_type,grant_perm', [
         ('all', 'all', 'repository.write'),
         ('dirs', 'dirs', 'repository.admin'),
         ('files', 'files', 'repository.read'),
@@ -841,7 +841,7 @@
         repo_name = u'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,
         )
         response = api_call(self, params)
@@ -857,7 +857,7 @@
         self._compare_ok(id_, expected, given=response.body)
         fixture.destroy_repo(repo_name)
 
-    @parametrize('repo_name', [
+    @base.parametrize('repo_name', [
         u'',
         u'.',
         u'..',
@@ -868,7 +868,7 @@
     def test_api_create_repo_bad_names(self, repo_name):
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,
         )
         response = api_call(self, params)
@@ -883,11 +883,11 @@
     def test_api_create_repo_clone_uri_local(self):
         # cloning from local repos was a mis-feature - it would bypass access control
         # TODO: introduce other test coverage of actual remote cloning
-        clone_uri = os.path.join(TESTS_TMP_PATH, self.REPO)
+        clone_uri = os.path.join(base.TESTS_TMP_PATH, self.REPO)
         repo_name = u'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,
                                   clone_uri=clone_uri,
         )
@@ -903,7 +903,7 @@
         # repo creation can no longer also create repo group
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = u'repo group `%s` not found' % repo_group_name
@@ -916,7 +916,7 @@
 
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = {
@@ -1036,7 +1036,7 @@
         repo_name = self.REPO
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = "repo `%s` already exist" % repo_name
@@ -1048,7 +1048,7 @@
         repo_name = '%s/%s' % (group_name, 'could-be-outside')
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = u'repo group `%s` not found' % group_name
@@ -1060,14 +1060,14 @@
         repo_name = u'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
                                   repo_type=self.REPO_TYPE,)
         response = api_call(self, params)
         expected = 'failed to create repository `%s`' % repo_name
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('changing_attr,updates', [
-        ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
+    @base.parametrize('changing_attr,updates', [
+        ('owner', {'owner': base.TEST_USER_REGULAR_LOGIN}),
         ('description', {'description': u'new description'}),
         ('clone_uri', {'clone_uri': 'http://example.com/repo'}), # will fail - pulling from non-existing repo should fail
         ('clone_uri', {'clone_uri': '/repo'}), # will fail - pulling from local repo was a mis-feature - it would bypass access control
@@ -1106,8 +1106,8 @@
             if changing_attr == 'repo_group':
                 fixture.destroy_repo_group(updates['group'])
 
-    @parametrize('changing_attr,updates', [
-        ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
+    @base.parametrize('changing_attr,updates', [
+        ('owner', {'owner': base.TEST_USER_REGULAR_LOGIN}),
         ('description', {'description': u'new description'}),
         ('clone_uri', {'clone_uri': 'http://example.com/repo'}), # will fail - pulling from non-existing repo should fail
         ('clone_uri', {'clone_uri': '/repo'}), # will fail - pulling from local repo was a mis-feature - it would bypass access control
@@ -1180,7 +1180,7 @@
         repo_name = u'api_update_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         id_, params = _build_data(self.apikey, 'update_repo',
-                                  repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
+                                  repoid=repo_name, owner=base.TEST_USER_ADMIN_LOGIN,)
         response = api_call(self, params)
         try:
             expected = 'failed to update repo `%s`' % repo_name
@@ -1237,7 +1237,7 @@
         RepoModel().grant_user_permission(repo=repo_name,
                                           user=self.TEST_USER_LOGIN,
                                           perm='repository.admin')
-        updates = {'owner': TEST_USER_ADMIN_LOGIN}
+        updates = {'owner': base.TEST_USER_ADMIN_LOGIN}
         id_, params = _build_data(self.apikey_regular, 'update_repo',
                                   repoid=repo_name, **updates)
         response = api_call(self, params)
@@ -1314,7 +1314,7 @@
         id_, params = _build_data(self.apikey, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
         )
         response = api_call(self, params)
 
@@ -1328,7 +1328,7 @@
         self._compare_ok(id_, expected, given=response.body)
         fixture.destroy_repo(fork_name)
 
-    @parametrize('fork_name', [
+    @base.parametrize('fork_name', [
         u'api-repo-fork',
         u'%s/api-repo-fork' % TEST_REPO_GROUP,
     ])
@@ -1354,7 +1354,7 @@
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
         )
         response = api_call(self, params)
         expected = 'Only Kallithea admin can specify `owner` param'
@@ -1380,7 +1380,7 @@
                                               perm='repository.read')
             fixture.destroy_repo(fork_name)
 
-    @parametrize('name,perm', [
+    @base.parametrize('name,perm', [
         ('read', 'repository.read'),
         ('write', 'repository.write'),
         ('admin', 'repository.admin'),
@@ -1425,7 +1425,7 @@
             id_, params = _build_data(self.apikey, 'fork_repo',
                                       repoid=self.REPO,
                                       fork_name=fork_name,
-                                      owner=TEST_USER_ADMIN_LOGIN,
+                                      owner=base.TEST_USER_ADMIN_LOGIN,
             )
             response = api_call(self, params)
 
@@ -1440,7 +1440,7 @@
         id_, params = _build_data(self.apikey, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
         )
         response = api_call(self, params)
 
@@ -1453,7 +1453,7 @@
         id_, params = _build_data(self.apikey, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
-                                  owner=TEST_USER_ADMIN_LOGIN,
+                                  owner=base.TEST_USER_ADMIN_LOGIN,
         )
         response = api_call(self, params)
 
@@ -1529,10 +1529,10 @@
         expected = 'failed to create group `%s`' % group_name
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('changing_attr,updates', [
+    @base.parametrize('changing_attr,updates', [
         ('group_name', {'group_name': u'new_group_name'}),
         ('group_name', {'group_name': u'test_group_for_update'}),
-        ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
+        ('owner', {'owner': base.TEST_USER_REGULAR_LOGIN}),
         ('active', {'active': False}),
         ('active', {'active': True}),
     ])
@@ -1574,11 +1574,11 @@
         try:
             id_, params = _build_data(self.apikey, 'add_user_to_user_group',
                                       usergroupid=gr_name,
-                                      userid=TEST_USER_ADMIN_LOGIN)
+                                      userid=base.TEST_USER_ADMIN_LOGIN)
             response = api_call(self, params)
             expected = {
             'msg': 'added member `%s` to user group `%s`' % (
-                    TEST_USER_ADMIN_LOGIN, gr_name),
+                    base.TEST_USER_ADMIN_LOGIN, gr_name),
             'success': True
             }
             self._compare_ok(id_, expected, given=response.body)
@@ -1588,7 +1588,7 @@
     def test_api_add_user_to_user_group_that_doesnt_exist(self):
         id_, params = _build_data(self.apikey, 'add_user_to_user_group',
                                   usergroupid='false-group',
-                                  userid=TEST_USER_ADMIN_LOGIN)
+                                  userid=base.TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
 
         expected = 'user group `%s` does not exist' % 'false-group'
@@ -1601,7 +1601,7 @@
         try:
             id_, params = _build_data(self.apikey, 'add_user_to_user_group',
                                       usergroupid=gr_name,
-                                      userid=TEST_USER_ADMIN_LOGIN)
+                                      userid=base.TEST_USER_ADMIN_LOGIN)
             response = api_call(self, params)
             expected = 'failed to add member to user group `%s`' % gr_name
             self._compare_error(id_, expected, given=response.body)
@@ -1611,16 +1611,16 @@
     def test_api_remove_user_from_user_group(self):
         gr_name = u'test_group_3'
         gr = fixture.create_user_group(gr_name)
-        UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
+        UserGroupModel().add_user_to_group(gr, user=base.TEST_USER_ADMIN_LOGIN)
         Session().commit()
         try:
             id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
                                       usergroupid=gr_name,
-                                      userid=TEST_USER_ADMIN_LOGIN)
+                                      userid=base.TEST_USER_ADMIN_LOGIN)
             response = api_call(self, params)
             expected = {
                 'msg': 'removed member `%s` from user group `%s`' % (
-                    TEST_USER_ADMIN_LOGIN, gr_name
+                    base.TEST_USER_ADMIN_LOGIN, gr_name
                 ),
                 'success': True}
             self._compare_ok(id_, expected, given=response.body)
@@ -1631,12 +1631,12 @@
     def test_api_remove_user_from_user_group_exception_occurred(self):
         gr_name = u'test_group_3'
         gr = fixture.create_user_group(gr_name)
-        UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
+        UserGroupModel().add_user_to_group(gr, user=base.TEST_USER_ADMIN_LOGIN)
         Session().commit()
         try:
             id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
                                       usergroupid=gr_name,
-                                      userid=TEST_USER_ADMIN_LOGIN)
+                                      userid=base.TEST_USER_ADMIN_LOGIN)
             response = api_call(self, params)
             expected = 'failed to remove member from user group `%s`' % gr_name
             self._compare_error(id_, expected, given=response.body)
@@ -1693,7 +1693,7 @@
         finally:
             fixture.destroy_user_group(gr_name)
 
-    @parametrize('name,perm', [
+    @base.parametrize('name,perm', [
         ('none', 'repository.none'),
         ('read', 'repository.read'),
         ('write', 'repository.write'),
@@ -1703,13 +1703,13 @@
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
         ret = {
             'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
-                perm, TEST_USER_ADMIN_LOGIN, self.REPO
+                perm, base.TEST_USER_ADMIN_LOGIN, self.REPO
             ),
             'success': True
         }
@@ -1721,7 +1721,7 @@
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
@@ -1734,12 +1734,12 @@
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
-            TEST_USER_ADMIN_LOGIN, self.REPO
+            base.TEST_USER_ADMIN_LOGIN, self.REPO
         )
         self._compare_error(id_, expected, given=response.body)
 
@@ -1747,12 +1747,12 @@
         id_, params = _build_data(self.apikey,
                                   'revoke_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN, )
+                                  userid=base.TEST_USER_ADMIN_LOGIN, )
         response = api_call(self, params)
 
         expected = {
             'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
-                TEST_USER_ADMIN_LOGIN, self.REPO
+                base.TEST_USER_ADMIN_LOGIN, self.REPO
             ),
             'success': True
         }
@@ -1763,15 +1763,15 @@
         id_, params = _build_data(self.apikey,
                                   'revoke_user_permission',
                                   repoid=self.REPO,
-                                  userid=TEST_USER_ADMIN_LOGIN, )
+                                  userid=base.TEST_USER_ADMIN_LOGIN, )
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
-            TEST_USER_ADMIN_LOGIN, self.REPO
+            base.TEST_USER_ADMIN_LOGIN, self.REPO
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,perm', [
+    @base.parametrize('name,perm', [
         ('none', 'repository.none'),
         ('read', 'repository.read'),
         ('write', 'repository.write'),
@@ -1853,7 +1853,7 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,perm,apply_to_children', [
+    @base.parametrize('name,perm,apply_to_children', [
         ('none', 'group.none', 'none'),
         ('read', 'group.read', 'none'),
         ('write', 'group.write', 'none'),
@@ -1878,20 +1878,20 @@
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission_to_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm, apply_to_children=apply_to_children)
         response = api_call(self, params)
 
         ret = {
             'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
-                perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                perm, apply_to_children, base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
             ),
             'success': True
         }
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
+    @base.parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
         ('none_fails', 'group.none', 'none', False, False),
         ('read_fails', 'group.read', 'none', False, False),
         ('write_fails', 'group.write', 'none', False, False),
@@ -1914,13 +1914,13 @@
         id_, params = _build_data(self.apikey_regular,
                                   'grant_user_permission_to_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm, apply_to_children=apply_to_children)
         response = api_call(self, params)
         if access_ok:
             ret = {
                 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
-                    perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                    perm, apply_to_children, base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
                 ),
                 'success': True
             }
@@ -1935,7 +1935,7 @@
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission_to_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
@@ -1948,16 +1948,16 @@
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission_to_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   perm=perm)
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
-            TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+            base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,apply_to_children', [
+    @base.parametrize('name,apply_to_children', [
         ('none', 'none'),
         ('all', 'all'),
         ('repos', 'repos'),
@@ -1965,26 +1965,26 @@
     ])
     def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
         RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
-                                               user=TEST_USER_ADMIN_LOGIN,
+                                               user=base.TEST_USER_ADMIN_LOGIN,
                                                perm='group.read',)
         Session().commit()
 
         id_, params = _build_data(self.apikey,
                                   'revoke_user_permission_from_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   apply_to_children=apply_to_children,)
         response = api_call(self, params)
 
         expected = {
             'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
-                apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                apply_to_children, base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
             ),
             'success': True
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    @parametrize('name,apply_to_children,grant_admin,access_ok', [
+    @base.parametrize('name,apply_to_children,grant_admin,access_ok', [
         ('none', 'none', False, False),
         ('all', 'all', False, False),
         ('repos', 'repos', False, False),
@@ -1999,7 +1999,7 @@
     def test_api_revoke_user_permission_from_repo_group_by_regular_user(
             self, name, apply_to_children, grant_admin, access_ok):
         RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
-                                               user=TEST_USER_ADMIN_LOGIN,
+                                               user=base.TEST_USER_ADMIN_LOGIN,
                                                perm='group.read',)
         Session().commit()
 
@@ -2012,13 +2012,13 @@
         id_, params = _build_data(self.apikey_regular,
                                   'revoke_user_permission_from_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN,
+                                  userid=base.TEST_USER_ADMIN_LOGIN,
                                   apply_to_children=apply_to_children,)
         response = api_call(self, params)
         if access_ok:
             expected = {
                 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
-                    apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                    apply_to_children, base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
                 ),
                 'success': True
             }
@@ -2032,15 +2032,15 @@
         id_, params = _build_data(self.apikey,
                                   'revoke_user_permission_from_repo_group',
                                   repogroupid=TEST_REPO_GROUP,
-                                  userid=TEST_USER_ADMIN_LOGIN, )
+                                  userid=base.TEST_USER_ADMIN_LOGIN, )
         response = api_call(self, params)
 
         expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
-            TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+            base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,perm,apply_to_children', [
+    @base.parametrize('name,perm,apply_to_children', [
         ('none', 'group.none', 'none'),
         ('read', 'group.read', 'none'),
         ('write', 'group.write', 'none'),
@@ -2079,7 +2079,7 @@
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
+    @base.parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
         ('none_fails', 'group.none', 'none', False, False),
         ('read_fails', 'group.read', 'none', False, False),
         ('write_fails', 'group.write', 'none', False, False),
@@ -2146,7 +2146,7 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parametrize('name,apply_to_children', [
+    @base.parametrize('name,apply_to_children', [
         ('none', 'none'),
         ('all', 'all'),
         ('repos', 'repos'),
@@ -2172,7 +2172,7 @@
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    @parametrize('name,apply_to_children,grant_admin,access_ok', [
+    @base.parametrize('name,apply_to_children,grant_admin,access_ok', [
         ('none', 'none', False, False),
         ('all', 'all', False, False),
         ('repos', 'repos', False, False),
@@ -2187,7 +2187,7 @@
     def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
             self, name, apply_to_children, grant_admin, access_ok):
         RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
-                                               user=TEST_USER_ADMIN_LOGIN,
+                                               user=base.TEST_USER_ADMIN_LOGIN,
                                                perm='group.read',)
         Session().commit()
 
@@ -2206,7 +2206,7 @@
         if access_ok:
             expected = {
                 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
-                    apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
+                    apply_to_children, base.TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
                 ),
                 'success': True
             }
@@ -2310,7 +2310,7 @@
 
     def test_api_get_gists_regular_user_with_different_userid(self):
         id_, params = _build_data(self.apikey_regular, 'get_gists',
-                                  userid=TEST_USER_ADMIN_LOGIN)
+                                  userid=base.TEST_USER_ADMIN_LOGIN)
         response = api_call(self, params)
         expected = 'userid is not the same as your user'
         self._compare_error(id_, expected, given=response.body)
@@ -2322,16 +2322,15 @@
                                   gist_type='public',
                                   files={'foobar': {'content': 'foo'}})
         response = api_call(self, params)
-        response_json = response.json
         expected = {
             'gist': {
-                'access_id': response_json['result']['gist']['access_id'],
-                'created_on': response_json['result']['gist']['created_on'],
+                'access_id': response.json['result']['gist']['access_id'],
+                'created_on': response.json['result']['gist']['created_on'],
                 'description': 'foobar-gist',
-                'expires': response_json['result']['gist']['expires'],
-                'gist_id': response_json['result']['gist']['gist_id'],
+                'expires': response.json['result']['gist']['expires'],
+                'gist_id': response.json['result']['gist']['gist_id'],
                 'type': 'public',
-                'url': response_json['result']['gist']['url']
+                'url': response.json['result']['gist']['url']
             },
             'msg': 'created new gist'
         }
@@ -2397,7 +2396,7 @@
         id_, params = _build_data(self.apikey, 'get_changesets',
                                   repoid=self.REPO, start=0, end=2)
         response = api_call(self, params)
-        result = json.loads(response.body)["result"]
+        result = ext_json.loads(response.body)["result"]
         assert len(result) == 3
         assert 'message' in result[0]
         assert 'added' not in result[0]
@@ -2406,7 +2405,7 @@
         id_, params = _build_data(self.apikey, 'get_changesets',
                                   repoid=self.REPO, start_date="2011-02-24T00:00:00", max_revisions=10)
         response = api_call(self, params)
-        result = json.loads(response.body)["result"]
+        result = ext_json.loads(response.body)["result"]
         assert len(result) == 10
         assert 'message' in result[0]
         assert 'added' not in result[0]
@@ -2419,7 +2418,7 @@
         id_, params = _build_data(self.apikey, 'get_changesets',
                                   repoid=self.REPO, branch_name=branch, start_date="2011-02-24T00:00:00")
         response = api_call(self, params)
-        result = json.loads(response.body)["result"]
+        result = ext_json.loads(response.body)["result"]
         assert len(result) == 5
         assert 'message' in result[0]
         assert 'added' not in result[0]
@@ -2428,7 +2427,7 @@
         id_, params = _build_data(self.apikey, 'get_changesets',
                                   repoid=self.REPO, start_date="2010-04-07T23:30:30", end_date="2010-04-08T00:31:14", with_file_list=True)
         response = api_call(self, params)
-        result = json.loads(response.body)["result"]
+        result = ext_json.loads(response.body)["result"]
         assert len(result) == 3
         assert 'message' in result[0]
         assert 'added' in result[0]
@@ -2438,7 +2437,7 @@
         id_, params = _build_data(self.apikey, 'get_changeset',
                                   repoid=self.REPO, raw_id=self.TEST_REVISION)
         response = api_call(self, params)
-        result = json.loads(response.body)["result"]
+        result = ext_json.loads(response.body)["result"]
         assert result["raw_id"] == self.TEST_REVISION
         assert "reviews" not in result
 
@@ -2448,7 +2447,7 @@
                                   repoid=self.REPO, raw_id=self.TEST_REVISION,
                                   with_reviews=True)
         response = api_call(self, params)
-        result = json.loads(response.body)["result"]
+        result = ext_json.loads(response.body)["result"]
         assert result["raw_id"] == self.TEST_REVISION
         assert "reviews" in result
         assert len(result["reviews"]) == 1
@@ -2484,12 +2483,12 @@
     def test_api_get_pullrequest(self):
         pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'get test')
         random_id = random.randrange(1, 9999)
-        params = json.dumps({
+        params = ascii_bytes(ext_json.dumps({
             "id": random_id,
             "api_key": self.apikey,
             "method": 'get_pullrequest',
             "args": {"pullrequest_id": pull_request_id},
-        })
+        }))
         response = api_call(self, params)
         pullrequest = PullRequest().get(pull_request_id)
         expected = {
@@ -2501,26 +2500,26 @@
             "org_repo_url": "http://localhost:80/%s" % self.REPO,
             "org_ref_parts": ["branch", "stable", self.TEST_PR_SRC],
             "other_ref_parts": ["branch", "default", self.TEST_PR_DST],
-            "comments": [{"username": TEST_USER_ADMIN_LOGIN, "text": "",
+            "comments": [{"username": base.TEST_USER_ADMIN_LOGIN, "text": "",
                          "comment_id": pullrequest.comments[0].comment_id}],
-            "owner": TEST_USER_ADMIN_LOGIN,
-            "statuses": [{"status": "under_review", "reviewer": TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00"} for i in range(0, len(self.TEST_PR_REVISIONS))],
+            "owner": base.TEST_USER_ADMIN_LOGIN,
+            "statuses": [{"status": "under_review", "reviewer": base.TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00"} for i in range(0, len(self.TEST_PR_REVISIONS))],
             "title": "get test",
             "revisions": self.TEST_PR_REVISIONS,
         }
         self._compare_ok(random_id, expected,
-                         given=re.sub("\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d",
-                                      "2000-01-01T00:00:00", response.body))
+                         given=re.sub(br"\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d",
+                                      b"2000-01-01T00:00:00", response.body))
 
     def test_api_close_pullrequest(self):
         pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'close test')
         random_id = random.randrange(1, 9999)
-        params = json.dumps({
+        params = ascii_bytes(ext_json.dumps({
             "id": random_id,
             "api_key": self.apikey,
             "method": "comment_pullrequest",
             "args": {"pull_request_id": pull_request_id, "close_pr": True},
-        })
+        }))
         response = api_call(self, params)
         self._compare_ok(random_id, True, given=response.body)
         pullrequest = PullRequest().get(pull_request_id)
@@ -2532,22 +2531,22 @@
         pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"status test")
 
         random_id = random.randrange(1, 9999)
-        params = json.dumps({
+        params = ascii_bytes(ext_json.dumps({
             "id": random_id,
-            "api_key": User.get_by_username(TEST_USER_REGULAR2_LOGIN).api_key,
+            "api_key": User.get_by_username(base.TEST_USER_REGULAR2_LOGIN).api_key,
             "method": "comment_pullrequest",
             "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
-        })
+        }))
         response = api_call(self, params)
         pullrequest = PullRequest().get(pull_request_id)
         self._compare_error(random_id, "No permission to change pull request status. User needs to be admin, owner or reviewer.", given=response.body)
         assert ChangesetStatus.STATUS_UNDER_REVIEW == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
-        params = json.dumps({
+        params = ascii_bytes(ext_json.dumps({
             "id": random_id,
-            "api_key": User.get_by_username(TEST_USER_REGULAR_LOGIN).api_key,
+            "api_key": User.get_by_username(base.TEST_USER_REGULAR_LOGIN).api_key,
             "method": "comment_pullrequest",
             "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
-        })
+        }))
         response = api_call(self, params)
         self._compare_ok(random_id, True, given=response.body)
         pullrequest = PullRequest().get(pull_request_id)
@@ -2556,12 +2555,12 @@
     def test_api_comment_pullrequest(self):
         pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"comment test")
         random_id = random.randrange(1, 9999)
-        params = json.dumps({
+        params = ascii_bytes(ext_json.dumps({
             "id": random_id,
             "api_key": self.apikey,
             "method": "comment_pullrequest",
             "args": {"pull_request_id": pull_request_id, "comment_msg": "Looks good to me"},
-        })
+        }))
         response = api_call(self, params)
         self._compare_ok(random_id, True, given=response.body)
         pullrequest = PullRequest().get(pull_request_id)
--- a/kallithea/tests/base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -22,7 +22,7 @@
 import pytest
 from webtest import TestApp
 
-from kallithea.lib.utils2 import safe_str
+from kallithea.lib.utils2 import ascii_str
 from kallithea.model.db import User
 
 
@@ -156,7 +156,7 @@
                                   'password': password,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
-        if 'Invalid username or password' in response.body:
+        if b'Invalid username or password' in response.body:
             pytest.fail('could not login using %s %s' % (username, password))
 
         assert response.status == '302 Found'
@@ -176,20 +176,19 @@
         assert user == expected_username
 
     def session_csrf_secret_token(self):
-        return self.app.get(url('session_csrf_secret_token')).body
+        return ascii_str(self.app.get(url('session_csrf_secret_token')).body)
 
     def checkSessionFlash(self, response, msg=None, skip=0, _matcher=lambda msg, m: msg in m):
         if 'flash' not in response.session:
-            pytest.fail(safe_str(u'msg `%s` not found - session has no flash:\n%s' % (msg, response)))
+            pytest.fail(u'msg `%s` not found - session has no flash:\n%s' % (msg, response))
         try:
             level, m = response.session['flash'][-1 - skip]
             if _matcher(msg, m):
                 return
         except IndexError:
             pass
-        pytest.fail(safe_str(u'msg `%s` not found in session flash (skipping %s): %s' %
-                           (msg, skip,
-                            ', '.join('`%s`' % m for level, m in response.session['flash']))))
+        pytest.fail(u'msg `%s` not found in session flash (skipping %s): %s' %
+                    (msg, skip, ', '.join('`%s`' % m for level, m in response.session['flash'])))
 
     def checkSessionFlashRegex(self, response, regex, skip=0):
         self.checkSessionFlash(response, regex, skip=skip, _matcher=re.search)
--- a/kallithea/tests/fixture.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/fixture.py	Thu Feb 06 01:19:23 2020 +0100
@@ -423,7 +423,7 @@
 
 
 def failing_test_hook(ui, repo, **kwargs):
-    ui.write("failing_test_hook failed\n")
+    ui.write(b"failing_test_hook failed\n")
     return 1
 
 
@@ -432,5 +432,5 @@
 
 
 def passing_test_hook(ui, repo, **kwargs):
-    ui.write("passing_test_hook succeeded\n")
+    ui.write(b"passing_test_hook succeeded\n")
     return 0
--- a/kallithea/tests/functional/test_admin.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin.py	Thu Feb 06 01:19:23 2020 +0100
@@ -3,16 +3,15 @@
 import os
 from os.path import dirname
 
-from kallithea.lib.utils2 import safe_unicode
 from kallithea.model.db import UserLog
 from kallithea.model.meta import Session
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
 FIXTURES = os.path.join(dirname(dirname(os.path.abspath(__file__))), 'fixtures')
 
 
-class TestAdminController(TestController):
+class TestAdminController(base.TestController):
 
     @classmethod
     def setup_class(cls):
@@ -34,8 +33,7 @@
         with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
             for row in csv.DictReader(f):
                 ul = UserLog()
-                for k, v in row.iteritems():
-                    v = safe_unicode(v)
+                for k, v in row.items():
                     if k == 'action_date':
                         v = strptime(v)
                     if k in ['user_id', 'repository_id']:
@@ -52,105 +50,105 @@
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index'))
+        response = self.app.get(base.url(controller='admin/admin', action='index'))
         response.mustcontain('Admin Journal')
 
     def test_filter_all_entries(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',))
+        response = self.app.get(base.url(controller='admin/admin', action='index',))
         response.mustcontain(' 2036 Entries')
 
     def test_filter_journal_filter_exact_match_on_repository(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:xxx'))
         response.mustcontain(' 3 Entries')
 
     def test_filter_journal_filter_exact_match_on_repository_CamelCase(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:XxX'))
         response.mustcontain(' 3 Entries')
 
     def test_filter_journal_filter_wildcard_on_repository(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:*test*'))
         response.mustcontain(' 862 Entries')
 
     def test_filter_journal_filter_prefix_on_repository(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:test*'))
         response.mustcontain(' 257 Entries')
 
     def test_filter_journal_filter_prefix_on_repository_CamelCase(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:Test*'))
         response.mustcontain(' 257 Entries')
 
     def test_filter_journal_filter_prefix_on_repository_and_user(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:test* AND username:demo'))
         response.mustcontain(' 130 Entries')
 
     def test_filter_journal_filter_prefix_on_repository_or_other_repo(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='repository:test* OR repository:xxx'))
         response.mustcontain(' 260 Entries')  # 257 + 3
 
     def test_filter_journal_filter_exact_match_on_username(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='username:demo'))
         response.mustcontain(' 1087 Entries')
 
     def test_filter_journal_filter_exact_match_on_username_camelCase(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='username:DemO'))
         response.mustcontain(' 1087 Entries')
 
     def test_filter_journal_filter_wildcard_on_username(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='username:*test*'))
         response.mustcontain(' 100 Entries')
 
     def test_filter_journal_filter_prefix_on_username(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='username:demo*'))
         response.mustcontain(' 1101 Entries')
 
     def test_filter_journal_filter_prefix_on_user_or_other_user(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='username:demo OR username:volcan'))
         response.mustcontain(' 1095 Entries')  # 1087 + 8
 
     def test_filter_journal_filter_wildcard_on_action(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='action:*pull_request*'))
         response.mustcontain(' 187 Entries')
 
     def test_filter_journal_filter_on_date(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='date:20121010'))
         response.mustcontain(' 47 Entries')
 
     def test_filter_journal_filter_on_date_2(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter='date:20121020'))
         response.mustcontain(' 17 Entries')
 
-    @parametrize('filter,hit', [
+    @base.parametrize('filter,hit', [
         #### "repository:" filtering
         # "/" is used for grouping
         ('repository:group/test', 4),
@@ -189,7 +187,7 @@
     def test_filter_journal_filter_tokenization(self, filter, hit):
         self.log_user()
 
-        response = self.app.get(url(controller='admin/admin', action='index',
+        response = self.app.get(base.url(controller='admin/admin', action='index',
                                     filter=filter))
         if hit != 1:
             response.mustcontain(' %s Entries' % hit)
--- a/kallithea/tests/functional/test_admin_auth_settings.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_auth_settings.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,10 +1,10 @@
 from kallithea.model.db import Setting
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestAuthSettingsController(TestController):
+class TestAuthSettingsController(base.TestController):
     def _enable_plugins(self, plugins_list):
-        test_url = url(controller='admin/auth_settings',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
         params={'auth_plugins': plugins_list, '_session_csrf_secret_token': self.session_csrf_secret_token()}
 
@@ -17,11 +17,11 @@
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='admin/auth_settings',
+        response = self.app.get(base.url(controller='admin/auth_settings',
                                     action='index'))
         response.mustcontain('Authentication Plugins')
 
-    @skipif(not ldap_lib_installed, reason='skipping due to missing ldap lib')
+    @base.skipif(not base.ldap_lib_installed, reason='skipping due to missing ldap lib')
     def test_ldap_save_settings(self):
         self.log_user()
 
@@ -41,7 +41,7 @@
                        'auth_ldap_attr_lastname': 'tester',
                        'auth_ldap_attr_email': 'test@example.com'})
 
-        test_url = url(controller='admin/auth_settings',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
 
         response = self.app.post(url=test_url, params=params)
@@ -50,7 +50,7 @@
         new_settings = Setting.get_auth_settings()
         assert new_settings['auth_ldap_host'] == u'dc.example.com', 'fail db write compare'
 
-    @skipif(not ldap_lib_installed, reason='skipping due to missing ldap lib')
+    @base.skipif(not base.ldap_lib_installed, reason='skipping due to missing ldap lib')
     def test_ldap_error_form_wrong_port_number(self):
         self.log_user()
 
@@ -68,7 +68,7 @@
                        'auth_ldap_attr_firstname': '',
                        'auth_ldap_attr_lastname': '',
                        'auth_ldap_attr_email': ''})
-        test_url = url(controller='admin/auth_settings',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
 
         response = self.app.post(url=test_url, params=params)
@@ -76,7 +76,7 @@
         response.mustcontain("""<span class="error-message">"""
                              """Please enter a number</span>""")
 
-    @skipif(not ldap_lib_installed, reason='skipping due to missing ldap lib')
+    @base.skipif(not base.ldap_lib_installed, reason='skipping due to missing ldap lib')
     def test_ldap_error_form(self):
         self.log_user()
 
@@ -95,7 +95,7 @@
                        'auth_ldap_attr_lastname': '',
                        'auth_ldap_attr_email': ''})
 
-        test_url = url(controller='admin/auth_settings',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
 
         response = self.app.post(url=test_url, params=params)
@@ -115,7 +115,7 @@
         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',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
 
         response = self.app.post(url=test_url, params=params)
@@ -124,7 +124,7 @@
 
     def _container_auth_verify_login(self, resulting_username, **get_kwargs):
         response = self.app.get(
-            url=url(controller='admin/my_account', action='my_account'),
+            url=base.url(controller='admin/my_account', action='my_account'),
             **get_kwargs
         )
         response.mustcontain('My Account %s' % resulting_username)
@@ -153,7 +153,7 @@
             auth_container_clean_username='False',
         )
         response = self.app.get(
-            url=url(controller='admin/my_account', action='my_account'),
+            url=base.url(controller='admin/my_account', action='my_account'),
             extra_environ={'THE_USER_NAME': 'johnd',
                            'THE_USER_EMAIL': 'john@example.org',
                            'THE_USER_FIRSTNAME': 'John',
@@ -216,10 +216,10 @@
             auth_container_clean_username='True',
         )
         response = self.app.get(
-            url=url(controller='admin/my_account', action='my_account'),
+            url=base.url(controller='admin/my_account', action='my_account'),
             extra_environ={'REMOTE_USER': 'john'},
         )
-        assert 'Log Out' not in response.normal_body
+        assert b'Log Out' not in response.normal_body
 
     def test_crowd_save_settings(self):
         self.log_user()
@@ -232,7 +232,7 @@
                        'auth_crowd_method': 'https',
                        'auth_crowd_app_name': 'xyzzy'})
 
-        test_url = url(controller='admin/auth_settings',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
 
         response = self.app.post(url=test_url, params=params)
@@ -241,7 +241,7 @@
         new_settings = Setting.get_auth_settings()
         assert new_settings['auth_crowd_host'] == u'hostname', 'fail db write compare'
 
-    @skipif(not pam_lib_installed, reason='skipping due to missing pam lib')
+    @base.skipif(not base.pam_lib_installed, reason='skipping due to missing pam lib')
     def test_pam_save_settings(self):
         self.log_user()
 
@@ -249,7 +249,7 @@
         params.update({'auth_pam_service': 'kallithea',
                        'auth_pam_gecos': '^foo-.*'})
 
-        test_url = url(controller='admin/auth_settings',
+        test_url = base.url(controller='admin/auth_settings',
                        action='auth_settings')
 
         response = self.app.post(url=test_url, params=params)
--- a/kallithea/tests/functional/test_admin_defaults.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_defaults.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,12 +1,12 @@
 from kallithea.model.db import Setting
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestDefaultsController(TestController):
+class TestDefaultsController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('defaults'))
+        response = self.app.get(base.url('defaults'))
         response.mustcontain('default_repo_private')
         response.mustcontain('default_repo_enable_statistics')
         response.mustcontain('default_repo_enable_downloads')
@@ -20,7 +20,7 @@
             'default_repo_type': 'hg',
             '_session_csrf_secret_token': self.session_csrf_secret_token(),
         }
-        response = self.app.post(url('defaults_update', id='default'), params=params)
+        response = self.app.post(base.url('defaults_update', id='default'), params=params)
         self.checkSessionFlash(response, 'Default settings updated successfully')
 
         params.pop('_session_csrf_secret_token')
@@ -36,7 +36,7 @@
             'default_repo_type': 'git',
             '_session_csrf_secret_token': self.session_csrf_secret_token(),
         }
-        response = self.app.post(url('defaults_update', id='default'), params=params)
+        response = self.app.post(base.url('defaults_update', id='default'), params=params)
         self.checkSessionFlash(response, 'Default settings updated successfully')
 
         params.pop('_session_csrf_secret_token')
--- a/kallithea/tests/functional/test_admin_gists.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_gists.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,24 +1,24 @@
 from kallithea.model.db import Gist, User
 from kallithea.model.gist import GistModel
 from kallithea.model.meta import Session
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
 def _create_gist(f_name, content='some gist', lifetime=-1,
                  description=u'gist-desc', gist_type='public',
-                 owner=TEST_USER_ADMIN_LOGIN):
+                 owner=base.TEST_USER_ADMIN_LOGIN):
     gist_mapping = {
         f_name: {'content': content}
     }
     owner = User.get_by_username(owner)
-    gist = GistModel().create(description, owner=owner, ip_addr=IP_ADDR,
+    gist = GistModel().create(description, owner=owner, ip_addr=base.IP_ADDR,
                        gist_mapping=gist_mapping, gist_type=gist_type,
                        lifetime=lifetime)
     Session().commit()
     return gist
 
 
-class TestGistsController(TestController):
+class TestGistsController(base.TestController):
 
     def teardown_method(self, method):
         for g in Gist.query():
@@ -27,7 +27,7 @@
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('gists'))
+        response = self.app.get(base.url('gists'))
         # Test response...
         response.mustcontain('There are no gists yet')
 
@@ -35,7 +35,7 @@
         g2 = _create_gist('gist2', lifetime=1400).gist_access_id
         g3 = _create_gist('gist3', description=u'gist3-desc').gist_access_id
         g4 = _create_gist('gist4', gist_type='private').gist_access_id
-        response = self.app.get(url('gists'))
+        response = self.app.get(base.url('gists'))
         # Test response...
         response.mustcontain('gist: %s' % g1)
         response.mustcontain('gist: %s' % g2)
@@ -47,7 +47,7 @@
     def test_index_private_gists(self):
         self.log_user()
         gist = _create_gist('gist5', gist_type='private')
-        response = self.app.get(url('gists', private=1))
+        response = self.app.get(base.url('gists', private=1))
         # Test response...
 
         # and privates
@@ -55,7 +55,7 @@
 
     def test_create_missing_description(self):
         self.log_user()
-        response = self.app.post(url('gists'),
+        response = self.app.post(base.url('gists'),
                                  params={'lifetime': -1, '_session_csrf_secret_token': self.session_csrf_secret_token()},
                                  status=200)
 
@@ -63,7 +63,7 @@
 
     def test_create(self):
         self.log_user()
-        response = self.app.post(url('gists'),
+        response = self.app.post(base.url('gists'),
                                  params={'lifetime': -1,
                                          'content': 'gist test',
                                          'filename': 'foo',
@@ -77,7 +77,7 @@
 
     def test_create_with_path_with_dirs(self):
         self.log_user()
-        response = self.app.post(url('gists'),
+        response = self.app.post(base.url('gists'),
                                  params={'lifetime': -1,
                                          'content': 'gist test',
                                          'filename': '/home/foo',
@@ -92,11 +92,11 @@
         gist.gist_expires = 0  # 1970
         Session().commit()
 
-        response = self.app.get(url('gist', gist_id=gist.gist_access_id), status=404)
+        response = self.app.get(base.url('gist', gist_id=gist.gist_access_id), status=404)
 
     def test_create_private(self):
         self.log_user()
-        response = self.app.post(url('gists'),
+        response = self.app.post(base.url('gists'),
                                  params={'lifetime': -1,
                                          'content': 'private gist test',
                                          'filename': 'private-foo',
@@ -110,7 +110,7 @@
 
     def test_create_with_description(self):
         self.log_user()
-        response = self.app.post(url('gists'),
+        response = self.app.post(base.url('gists'),
                                  params={'lifetime': -1,
                                          'content': 'gist test',
                                          'filename': 'foo-desc',
@@ -126,46 +126,46 @@
 
     def test_new(self):
         self.log_user()
-        response = self.app.get(url('new_gist'))
+        response = self.app.get(base.url('new_gist'))
 
     def test_delete(self):
         self.log_user()
         gist = _create_gist('delete-me')
-        response = self.app.post(url('gist_delete', gist_id=gist.gist_id),
+        response = self.app.post(base.url('gist_delete', gist_id=gist.gist_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
     def test_delete_normal_user_his_gist(self):
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-        gist = _create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
-        response = self.app.post(url('gist_delete', gist_id=gist.gist_id),
+        self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)
+        gist = _create_gist('delete-me', owner=base.TEST_USER_REGULAR_LOGIN)
+        response = self.app.post(base.url('gist_delete', gist_id=gist.gist_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
     def test_delete_normal_user_not_his_own_gist(self):
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)
         gist = _create_gist('delete-me')
-        response = self.app.post(url('gist_delete', gist_id=gist.gist_id), status=403,
+        response = self.app.post(base.url('gist_delete', gist_id=gist.gist_id), status=403,
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
     def test_show(self):
         gist = _create_gist('gist-show-me')
-        response = self.app.get(url('gist', gist_id=gist.gist_access_id))
+        response = self.app.get(base.url('gist', gist_id=gist.gist_access_id))
         response.mustcontain('added file: gist-show-me<')
-        response.mustcontain('%s - created' % TEST_USER_ADMIN_LOGIN)
+        response.mustcontain('%s - created' % base.TEST_USER_ADMIN_LOGIN)
         response.mustcontain('gist-desc')
         response.mustcontain('<div class="label label-success">Public Gist</div>')
 
     def test_show_as_raw(self):
         gist = _create_gist('gist-show-me', content='GIST CONTENT')
-        response = self.app.get(url('formatted_gist',
+        response = self.app.get(base.url('formatted_gist',
                                     gist_id=gist.gist_access_id, format='raw'))
-        assert response.body == 'GIST CONTENT'
+        assert response.body == b'GIST CONTENT'
 
     def test_show_as_raw_individual_file(self):
         gist = _create_gist('gist-show-me-raw', content='GIST BODY')
-        response = self.app.get(url('formatted_gist_file',
+        response = self.app.get(base.url('formatted_gist_file',
                                     gist_id=gist.gist_access_id, format='raw',
                                     revision='tip', f_path='gist-show-me-raw'))
-        assert response.body == 'GIST BODY'
+        assert response.body == b'GIST BODY'
 
     def test_edit(self):
-        response = self.app.get(url('edit_gist', gist_id=1))
+        response = self.app.get(base.url('edit_gist', gist_id=1))
--- a/kallithea/tests/functional/test_admin_permissions.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_permissions.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,17 +1,17 @@
 from kallithea.model.db import User, UserIpMap
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestAdminPermissionsController(TestController):
+class TestAdminPermissionsController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('admin_permissions'))
+        response = self.app.get(base.url('admin_permissions'))
         # Test response...
 
     def test_index_ips(self):
         self.log_user()
-        response = self.app.get(url('admin_permissions_ips'))
+        response = self.app.get(base.url('admin_permissions_ips'))
         # Test response...
         response.mustcontain('All IP addresses are allowed')
 
@@ -21,61 +21,61 @@
 
         # Add IP and verify it is shown in UI and both gives access and rejects
 
-        response = self.app.post(url('edit_user_ips_update', id=default_user_id),
+        response = self.app.post(base.url('edit_user_ips_update', id=default_user_id),
                                  params=dict(new_ip='0.0.0.0/24',
                                  _session_csrf_secret_token=self.session_csrf_secret_token()))
-        invalidate_all_caches()
-        response = self.app.get(url('admin_permissions_ips'),
+        base.invalidate_all_caches()
+        response = self.app.get(base.url('admin_permissions_ips'),
                                 extra_environ={'REMOTE_ADDR': '0.0.0.1'})
         response.mustcontain('0.0.0.0/24')
         response.mustcontain('0.0.0.0 - 0.0.0.255')
 
-        response = self.app.get(url('admin_permissions_ips'),
+        response = self.app.get(base.url('admin_permissions_ips'),
                                 extra_environ={'REMOTE_ADDR': '0.0.1.1'}, status=403)
 
         # Add another IP and verify previously rejected now works
 
-        response = self.app.post(url('edit_user_ips_update', id=default_user_id),
+        response = self.app.post(base.url('edit_user_ips_update', id=default_user_id),
                                  params=dict(new_ip='0.0.1.0/24',
                                  _session_csrf_secret_token=self.session_csrf_secret_token()))
-        invalidate_all_caches()
+        base.invalidate_all_caches()
 
-        response = self.app.get(url('admin_permissions_ips'),
+        response = self.app.get(base.url('admin_permissions_ips'),
                                 extra_environ={'REMOTE_ADDR': '0.0.1.1'})
 
         # Delete latest IP and verify same IP is rejected again
 
         x = UserIpMap.query().filter_by(ip_addr='0.0.1.0/24').first()
-        response = self.app.post(url('edit_user_ips_delete', id=default_user_id),
+        response = self.app.post(base.url('edit_user_ips_delete', id=default_user_id),
                                  params=dict(del_ip_id=x.ip_id,
                                              _session_csrf_secret_token=self.session_csrf_secret_token()))
-        invalidate_all_caches()
+        base.invalidate_all_caches()
 
-        response = self.app.get(url('admin_permissions_ips'),
+        response = self.app.get(base.url('admin_permissions_ips'),
                                 extra_environ={'REMOTE_ADDR': '0.0.1.1'}, status=403)
 
         # Delete first IP and verify unlimited access again
 
         x = UserIpMap.query().filter_by(ip_addr='0.0.0.0/24').first()
-        response = self.app.post(url('edit_user_ips_delete', id=default_user_id),
+        response = self.app.post(base.url('edit_user_ips_delete', id=default_user_id),
                                  params=dict(del_ip_id=x.ip_id,
                                              _session_csrf_secret_token=self.session_csrf_secret_token()))
-        invalidate_all_caches()
+        base.invalidate_all_caches()
 
-        response = self.app.get(url('admin_permissions_ips'),
+        response = self.app.get(base.url('admin_permissions_ips'),
                                 extra_environ={'REMOTE_ADDR': '0.0.1.1'})
 
     def test_index_overview(self):
         self.log_user()
-        response = self.app.get(url('admin_permissions_perms'))
+        response = self.app.get(base.url('admin_permissions_perms'))
         # Test response...
 
     def test_edit_permissions_permissions(self):
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
 
         # Test unauthenticated access - it will redirect to login page
         response = self.app.post(
-            url('edit_repo_perms_update', repo_name=HG_REPO),
+            base.url('edit_repo_perms_update', repo_name=base.HG_REPO),
             params=dict(
                 perm_new_member_1='repository.read',
                 perm_new_member_name_1=user.username,
@@ -83,24 +83,24 @@
                 _session_csrf_secret_token=self.session_csrf_secret_token()),
             status=302)
 
-        assert not response.location.endswith(url('edit_repo_perms_update', repo_name=HG_REPO))
-        assert response.location.endswith(url('login_home', came_from=url('edit_repo_perms_update', repo_name=HG_REPO)))
+        assert not response.location.endswith(base.url('edit_repo_perms_update', repo_name=base.HG_REPO))
+        assert response.location.endswith(base.url('login_home', came_from=base.url('edit_repo_perms_update', repo_name=base.HG_REPO)))
 
         response = self.app.post(
-            url('edit_repo_perms_revoke', repo_name=HG_REPO),
+            base.url('edit_repo_perms_revoke', repo_name=base.HG_REPO),
             params=dict(
                 obj_type='user',
                 user_id=user.user_id,
                 _session_csrf_secret_token=self.session_csrf_secret_token()),
             status=302)
 
-        assert response.location.endswith(url('login_home', came_from=url('edit_repo_perms_revoke', repo_name=HG_REPO)))
+        assert response.location.endswith(base.url('login_home', came_from=base.url('edit_repo_perms_revoke', repo_name=base.HG_REPO)))
 
         # Test authenticated access
         self.log_user()
 
         response = self.app.post(
-            url('edit_repo_perms_update', repo_name=HG_REPO),
+            base.url('edit_repo_perms_update', repo_name=base.HG_REPO),
             params=dict(
                 perm_new_member_1='repository.read',
                 perm_new_member_name_1=user.username,
@@ -108,10 +108,10 @@
                 _session_csrf_secret_token=self.session_csrf_secret_token()),
             status=302)
 
-        assert response.location.endswith(url('edit_repo_perms_update', repo_name=HG_REPO))
+        assert response.location.endswith(base.url('edit_repo_perms_update', repo_name=base.HG_REPO))
 
         response = self.app.post(
-            url('edit_repo_perms_revoke', repo_name=HG_REPO),
+            base.url('edit_repo_perms_revoke', repo_name=base.HG_REPO),
             params=dict(
                 obj_type='user',
                 user_id=user.user_id,
--- a/kallithea/tests/functional/test_admin_repos.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_repos.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,19 +1,18 @@
 # -*- coding: utf-8 -*-
 
 import os
-import urllib
+import urllib.parse
 
 import mock
 import pytest
 
 from kallithea.lib import vcs
-from kallithea.lib.utils2 import safe_str, safe_unicode
 from kallithea.model.db import Permission, RepoGroup, Repository, Ui, User, UserRepoToPerm
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture, error_function
 
 
@@ -29,7 +28,7 @@
     return perm
 
 
-class _BaseTestCase(TestController):
+class _BaseTestCase(base.TestController):
     """
     Write all tests here
     """
@@ -41,20 +40,20 @@
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('repos'))
+        response = self.app.get(base.url('repos'))
 
     def test_create(self):
         self.log_user()
         repo_name = self.NEW_REPO
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
                                                 repo_description=description,
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name))
+        response = self.app.get(base.url('repo_check_home', repo_name=repo_name))
         assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
@@ -68,13 +67,13 @@
         assert new_repo.description == description
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name))
+        response = self.app.get(base.url('summary_home', repo_name=repo_name))
         response.mustcontain(repo_name)
         response.mustcontain(self.REPO_TYPE)
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name)))
+            vcs.get_repo(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name))
         except vcs.exceptions.VCSError:
             pytest.fail('no repo %s in filesystem' % repo_name)
 
@@ -85,7 +84,7 @@
         self.log_user()
         repo_name = self.NEW_REPO
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                                  fixture._get_repo_create_params(repo_private=False,
                                                                  repo_name=repo_name,
                                                                  repo_type=self.REPO_TYPE,
@@ -93,7 +92,7 @@
                                                                  _session_csrf_secret_token=self.session_csrf_secret_token()))
         # try to create repo with swapped case
         swapped_repo_name = repo_name.swapcase()
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                                  fixture._get_repo_create_params(repo_private=False,
                                                                  repo_name=swapped_repo_name,
                                                                  repo_type=self.REPO_TYPE,
@@ -111,13 +110,13 @@
         group_name = u'sometest_%s' % self.REPO_TYPE
         gr = RepoGroupModel().create(group_name=group_name,
                                      group_description=u'test',
-                                     owner=TEST_USER_ADMIN_LOGIN)
+                                     owner=base.TEST_USER_ADMIN_LOGIN)
         Session().commit()
 
         repo_name = u'ingroup'
         repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -125,7 +124,7 @@
                                                 repo_group=gr.group_id,
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
+        response = self.app.get(base.url('repo_check_home', repo_name=repo_name_full))
         assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
@@ -139,7 +138,7 @@
         assert new_repo.description == description
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name_full))
+        response = self.app.get(base.url('summary_home', repo_name=repo_name_full))
         response.mustcontain(repo_name_full)
         response.mustcontain(self.REPO_TYPE)
 
@@ -149,7 +148,7 @@
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full)))
+            vcs.get_repo(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full))
         except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
@@ -160,7 +159,7 @@
         Session().commit()
 
     def test_create_in_group_without_needed_permissions(self):
-        usr = self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        usr = self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)
         # avoid spurious RepoGroup DetachedInstanceError ...
         session_csrf_secret_token = self.session_csrf_secret_token()
         # revoke
@@ -172,29 +171,29 @@
         user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
 
         # disable on regular user
-        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
-        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
-        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
-        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
+        user_model.revoke_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
+        user_model.grant_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.create.none')
+        user_model.revoke_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
+        user_model.grant_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
         Session().commit()
 
         ## create GROUP
         group_name = u'reg_sometest_%s' % self.REPO_TYPE
         gr = RepoGroupModel().create(group_name=group_name,
                                      group_description=u'test',
-                                     owner=TEST_USER_ADMIN_LOGIN)
+                                     owner=base.TEST_USER_ADMIN_LOGIN)
         Session().commit()
 
         group_name_allowed = u'reg_sometest_allowed_%s' % self.REPO_TYPE
         gr_allowed = RepoGroupModel().create(group_name=group_name_allowed,
                                      group_description=u'test',
-                                     owner=TEST_USER_REGULAR_LOGIN)
+                                     owner=base.TEST_USER_REGULAR_LOGIN)
         Session().commit()
 
         repo_name = u'ingroup'
         repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -208,7 +207,7 @@
         repo_name = u'ingroup'
         repo_name_full = RepoGroup.url_sep().join([group_name_allowed, repo_name])
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -217,7 +216,7 @@
                                                 _session_csrf_secret_token=session_csrf_secret_token))
 
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
+        response = self.app.get(base.url('repo_check_home', repo_name=repo_name_full))
         assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
@@ -231,7 +230,7 @@
         assert new_repo.description == description
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name_full))
+        response = self.app.get(base.url('summary_home', repo_name=repo_name_full))
         response.mustcontain(repo_name_full)
         response.mustcontain(self.REPO_TYPE)
 
@@ -241,7 +240,7 @@
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full)))
+            vcs.get_repo(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full))
         except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
@@ -259,9 +258,9 @@
         group_name = u'sometest_%s' % self.REPO_TYPE
         gr = RepoGroupModel().create(group_name=group_name,
                                      group_description=u'test',
-                                     owner=TEST_USER_ADMIN_LOGIN)
+                                     owner=base.TEST_USER_ADMIN_LOGIN)
         perm = Permission.get_by_key('repository.write')
-        RepoGroupModel().grant_user_permission(gr, TEST_USER_REGULAR_LOGIN, perm)
+        RepoGroupModel().grant_user_permission(gr, base.TEST_USER_REGULAR_LOGIN, perm)
 
         ## add repo permissions
         Session().commit()
@@ -269,7 +268,7 @@
         repo_name = u'ingroup_inherited_%s' % self.REPO_TYPE
         repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -279,7 +278,7 @@
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
 
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
+        response = self.app.get(base.url('repo_check_home', repo_name=repo_name_full))
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name_full, repo_name_full))
@@ -292,13 +291,13 @@
         assert new_repo.description == description
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name_full))
+        response = self.app.get(base.url('summary_home', repo_name=repo_name_full))
         response.mustcontain(repo_name_full)
         response.mustcontain(self.REPO_TYPE)
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full)))
+            vcs.get_repo(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full))
         except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
@@ -309,7 +308,7 @@
             .filter(UserRepoToPerm.repository_id == new_repo_id).all()
         assert len(inherited_perms) == 2
 
-        assert TEST_USER_REGULAR_LOGIN in [x.user.username
+        assert base.TEST_USER_REGULAR_LOGIN in [x.user.username
                                                     for x in inherited_perms]
         assert 'repository.write' in [x.permission.permission_name
                                                for x in inherited_perms]
@@ -322,7 +321,7 @@
         self.log_user()
         repo_name = self.NEW_REPO
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -335,7 +334,7 @@
         self.log_user()
         repo_name = self.NEW_REPO
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -348,14 +347,14 @@
         self.log_user()
         repo_name = u'vcs_test_new_to_delete_%s' % self.REPO_TYPE
         description = u'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_type=self.REPO_TYPE,
                                                 repo_name=repo_name,
                                                 repo_description=description,
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name))
+        response = self.app.get(base.url('repo_check_home', repo_name=repo_name))
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name, repo_name))
@@ -367,17 +366,17 @@
         assert new_repo.description == description
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name))
+        response = self.app.get(base.url('summary_home', repo_name=repo_name))
         response.mustcontain(repo_name)
         response.mustcontain(self.REPO_TYPE)
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name)))
+            vcs.get_repo(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name))
         except vcs.exceptions.VCSError:
             pytest.fail('no repo %s in filesystem' % repo_name)
 
-        response = self.app.post(url('delete_repo', repo_name=repo_name),
+        response = self.app.post(base.url('delete_repo', repo_name=repo_name),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name))
@@ -395,67 +394,65 @@
     def test_delete_non_ascii(self):
         self.log_user()
         non_ascii = "ąęł"
-        repo_name = "%s%s" % (safe_str(self.NEW_REPO), non_ascii)
-        repo_name_unicode = safe_unicode(repo_name)
+        repo_name = "%s%s" % (self.NEW_REPO, non_ascii)
         description = 'description for newly created repo' + non_ascii
-        description_unicode = safe_unicode(description)
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
                                                 repo_description=description,
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name))
+        response = self.app.get(base.url('repo_check_home', repo_name=repo_name))
         assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                u'Created repository <a href="/%s">%s</a>'
-                               % (urllib.quote(repo_name), repo_name_unicode))
+                               % (urllib.parse.quote(repo_name), repo_name))
         # test if the repo was created in the database
         new_repo = Session().query(Repository) \
-            .filter(Repository.repo_name == repo_name_unicode).one()
+            .filter(Repository.repo_name == repo_name).one()
 
-        assert new_repo.repo_name == repo_name_unicode
-        assert new_repo.description == description_unicode
+        assert new_repo.repo_name == repo_name
+        assert new_repo.description == description
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name))
+        response = self.app.get(base.url('summary_home', repo_name=repo_name))
         response.mustcontain(repo_name)
         response.mustcontain(self.REPO_TYPE)
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_unicode)))
+            vcs.get_repo(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name))
         except vcs.exceptions.VCSError:
             pytest.fail('no repo %s in filesystem' % repo_name)
 
-        response = self.app.post(url('delete_repo', repo_name=repo_name),
+        response = self.app.post(base.url('delete_repo', repo_name=repo_name),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
-        self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name_unicode))
+        self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name))
         response.follow()
 
         # check if repo was deleted from db
         deleted_repo = Session().query(Repository) \
-            .filter(Repository.repo_name == repo_name_unicode).scalar()
+            .filter(Repository.repo_name == repo_name).scalar()
 
         assert deleted_repo is None
 
-        assert os.path.isdir(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_unicode)) == False
+        assert os.path.isdir(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name)) == False
 
     def test_delete_repo_with_group(self):
         # TODO:
         pass
 
     def test_delete_browser_fakeout(self):
-        response = self.app.post(url('delete_repo', repo_name=self.REPO),
+        response = self.app.post(base.url('delete_repo', repo_name=self.REPO),
                                  params=dict(_session_csrf_secret_token=self.session_csrf_secret_token()))
 
     def test_show(self):
         self.log_user()
-        response = self.app.get(url('summary_home', repo_name=self.REPO))
+        response = self.app.get(base.url('summary_home', repo_name=self.REPO))
 
     def test_edit(self):
-        response = self.app.get(url('edit_repo', repo_name=self.REPO))
+        response = self.app.get(base.url('edit_repo', repo_name=self.REPO))
 
     def test_set_private_flag_sets_default_to_none(self):
         self.log_user()
@@ -465,11 +462,11 @@
         assert perm[0].permission.permission_name == 'repository.read'
         assert Repository.get_by_repo_name(self.REPO).private == False
 
-        response = self.app.post(url('update_repo', repo_name=self.REPO),
+        response = self.app.post(base.url('update_repo', repo_name=self.REPO),
                         fixture._get_repo_create_params(repo_private=1,
                                                 repo_name=self.REPO,
                                                 repo_type=self.REPO_TYPE,
-                                                owner=TEST_USER_ADMIN_LOGIN,
+                                                owner=base.TEST_USER_ADMIN_LOGIN,
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         self.checkSessionFlash(response,
                                msg='Repository %s updated successfully' % (self.REPO))
@@ -480,11 +477,11 @@
         assert len(perm), 1
         assert perm[0].permission.permission_name == 'repository.none'
 
-        response = self.app.post(url('update_repo', repo_name=self.REPO),
+        response = self.app.post(base.url('update_repo', repo_name=self.REPO),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=self.REPO,
                                                 repo_type=self.REPO_TYPE,
-                                                owner=TEST_USER_ADMIN_LOGIN,
+                                                owner=base.TEST_USER_ADMIN_LOGIN,
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         self.checkSessionFlash(response,
                                msg='Repository %s updated successfully' % (self.REPO))
@@ -502,7 +499,7 @@
     def test_set_repo_fork_has_no_self_id(self):
         self.log_user()
         repo = Repository.get_by_repo_name(self.REPO)
-        response = self.app.get(url('edit_repo_advanced', repo_name=self.REPO))
+        response = self.app.get(base.url('edit_repo_advanced', repo_name=self.REPO))
         opt = """<option value="%s">%s</option>""" % (repo.repo_id, self.REPO)
         response.mustcontain(no=[opt])
 
@@ -512,7 +509,7 @@
         fixture.create_repo(other_repo, repo_type=self.REPO_TYPE)
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(other_repo)
-        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(base.url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=repo2.repo_id, _session_csrf_secret_token=self.session_csrf_secret_token()))
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(other_repo)
@@ -533,7 +530,7 @@
         self.log_user()
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
-        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(base.url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=repo2.repo_id, _session_csrf_secret_token=self.session_csrf_secret_token()))
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
@@ -543,7 +540,7 @@
     def test_set_fork_of_none(self):
         self.log_user()
         ## mark it as None
-        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(base.url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=None, _session_csrf_secret_token=self.session_csrf_secret_token()))
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
@@ -555,13 +552,13 @@
     def test_set_fork_of_same_repo(self):
         self.log_user()
         repo = Repository.get_by_repo_name(self.REPO)
-        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(base.url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=repo.repo_id, _session_csrf_secret_token=self.session_csrf_secret_token()))
         self.checkSessionFlash(response,
                                'An error occurred during this operation')
 
     def test_create_on_top_level_without_permissions(self):
-        usr = self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        usr = self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)
         # revoke
         user_model = UserModel()
         # disable fork and create on default user
@@ -571,10 +568,10 @@
         user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
 
         # disable on regular user
-        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
-        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
-        user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
-        user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
+        user_model.revoke_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
+        user_model.grant_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.create.none')
+        user_model.revoke_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
+        user_model.grant_perm(base.TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
         Session().commit()
 
 
@@ -582,7 +579,7 @@
 
         repo_name = self.NEW_REPO + u'no_perms'
         description = 'description for newly created repo'
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -600,7 +597,7 @@
         repo_name = self.NEW_REPO
         description = 'description for newly created repo'
 
-        response = self.app.post(url('repos'),
+        response = self.app.post(base.url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
                                                 repo_type=self.REPO_TYPE,
@@ -618,18 +615,18 @@
 
 
 class TestAdminReposControllerGIT(_BaseTestCase):
-    REPO = GIT_REPO
+    REPO = base.GIT_REPO
     REPO_TYPE = 'git'
-    NEW_REPO = NEW_GIT_REPO
-    OTHER_TYPE_REPO = HG_REPO
+    NEW_REPO = base.NEW_GIT_REPO
+    OTHER_TYPE_REPO = base.HG_REPO
     OTHER_TYPE = 'hg'
 
 
 class TestAdminReposControllerHG(_BaseTestCase):
-    REPO = HG_REPO
+    REPO = base.HG_REPO
     REPO_TYPE = 'hg'
-    NEW_REPO = NEW_HG_REPO
-    OTHER_TYPE_REPO = GIT_REPO
+    NEW_REPO = base.NEW_HG_REPO
+    OTHER_TYPE_REPO = base.GIT_REPO
     OTHER_TYPE = 'git'
 
     def test_permanent_url_protocol_access(self):
@@ -637,7 +634,7 @@
         permanent_name = '_%d' % repo.repo_id
 
         # 400 Bad Request - Unable to detect pull/push action
-        self.app.get(url('summary_home', repo_name=permanent_name),
+        self.app.get(base.url('summary_home', repo_name=permanent_name),
             extra_environ={'HTTP_ACCEPT': 'application/mercurial'},
             status=400,
         )
--- a/kallithea/tests/functional/test_admin_settings.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_settings.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,54 +1,54 @@
 # -*- coding: utf-8 -*-
 
 from kallithea.model.db import Setting, Ui
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestAdminSettingsController(TestController):
+class TestAdminSettingsController(base.TestController):
 
     def test_index_main(self):
         self.log_user()
-        response = self.app.get(url('admin_settings'))
+        response = self.app.get(base.url('admin_settings'))
 
     def test_index_mapping(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_mapping'))
+        response = self.app.get(base.url('admin_settings_mapping'))
 
     def test_index_global(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_global'))
+        response = self.app.get(base.url('admin_settings_global'))
 
     def test_index_visual(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_visual'))
+        response = self.app.get(base.url('admin_settings_visual'))
 
     def test_index_email(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_email'))
+        response = self.app.get(base.url('admin_settings_email'))
 
     def test_index_hooks(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_hooks'))
+        response = self.app.get(base.url('admin_settings_hooks'))
 
     def test_create_custom_hook(self):
         self.log_user()
-        response = self.app.post(url('admin_settings_hooks'),
+        response = self.app.post(base.url('admin_settings_hooks'),
                                 params=dict(new_hook_ui_key='test_hooks_1',
-                                            new_hook_ui_value='cd %s' % TESTS_TMP_PATH,
+                                            new_hook_ui_value='cd %s' % base.TESTS_TMP_PATH,
                                             _session_csrf_secret_token=self.session_csrf_secret_token()))
 
         self.checkSessionFlash(response, 'Added new hook')
         response = response.follow()
         response.mustcontain('test_hooks_1')
-        response.mustcontain('cd %s' % TESTS_TMP_PATH)
+        response.mustcontain('cd %s' % base.TESTS_TMP_PATH)
 
     def test_edit_custom_hook(self):
         self.log_user()
-        response = self.app.post(url('admin_settings_hooks'),
+        response = self.app.post(base.url('admin_settings_hooks'),
                                 params=dict(hook_ui_key='test_hooks_1',
                                             hook_ui_value='old_value_of_hook_1',
                                             hook_ui_value_new='new_value_of_hook_1',
@@ -60,7 +60,7 @@
 
     def test_add_existing_custom_hook(self):
         self.log_user()
-        response = self.app.post(url('admin_settings_hooks'),
+        response = self.app.post(base.url('admin_settings_hooks'),
                                 params=dict(new_hook_ui_key='test_hooks_1',
                                             new_hook_ui_value='attempted_new_value',
                                             _session_csrf_secret_token=self.session_csrf_secret_token()))
@@ -72,27 +72,27 @@
 
     def test_create_custom_hook_delete(self):
         self.log_user()
-        response = self.app.post(url('admin_settings_hooks'),
+        response = self.app.post(base.url('admin_settings_hooks'),
                                 params=dict(new_hook_ui_key='test_hooks_2',
-                                            new_hook_ui_value='cd %s2' % TESTS_TMP_PATH,
+                                            new_hook_ui_value='cd %s2' % base.TESTS_TMP_PATH,
                                             _session_csrf_secret_token=self.session_csrf_secret_token()))
 
         self.checkSessionFlash(response, 'Added new hook')
         response = response.follow()
         response.mustcontain('test_hooks_2')
-        response.mustcontain('cd %s2' % TESTS_TMP_PATH)
+        response.mustcontain('cd %s2' % base.TESTS_TMP_PATH)
 
         hook_id = Ui.get_by_key('hooks', 'test_hooks_2').ui_id
         ## delete
-        self.app.post(url('admin_settings_hooks'),
+        self.app.post(base.url('admin_settings_hooks'),
                         params=dict(hook_id=hook_id, _session_csrf_secret_token=self.session_csrf_secret_token()))
-        response = self.app.get(url('admin_settings_hooks'))
+        response = self.app.get(base.url('admin_settings_hooks'))
         response.mustcontain(no=['test_hooks_2'])
-        response.mustcontain(no=['cd %s2' % TESTS_TMP_PATH])
+        response.mustcontain(no=['cd %s2' % base.TESTS_TMP_PATH])
 
     def test_add_existing_builtin_hook(self):
         self.log_user()
-        response = self.app.post(url('admin_settings_hooks'),
+        response = self.app.post(base.url('admin_settings_hooks'),
                                 params=dict(new_hook_ui_key='changegroup.update',
                                             new_hook_ui_value='attempted_new_value',
                                             _session_csrf_secret_token=self.session_csrf_secret_token()))
@@ -104,18 +104,18 @@
 
     def test_index_search(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_search'))
+        response = self.app.get(base.url('admin_settings_search'))
 
     def test_index_system(self):
         self.log_user()
-        response = self.app.get(url('admin_settings_system'))
+        response = self.app.get(base.url('admin_settings_system'))
 
     def test_ga_code_active(self):
         self.log_user()
         old_title = 'Kallithea'
         old_realm = 'Kallithea authentication'
         new_ga_code = 'ga-test-123456789'
-        response = self.app.post(url('admin_settings_global'),
+        response = self.app.post(base.url('admin_settings_global'),
                         params=dict(title=old_title,
                                  realm=old_realm,
                                  ga_code=new_ga_code,
@@ -136,7 +136,7 @@
         old_title = 'Kallithea'
         old_realm = 'Kallithea authentication'
         new_ga_code = ''
-        response = self.app.post(url('admin_settings_global'),
+        response = self.app.post(base.url('admin_settings_global'),
                         params=dict(title=old_title,
                                  realm=old_realm,
                                  ga_code=new_ga_code,
@@ -156,7 +156,7 @@
         old_title = 'Kallithea'
         old_realm = 'Kallithea authentication'
         new_ga_code = ''
-        response = self.app.post(url('admin_settings_global'),
+        response = self.app.post(base.url('admin_settings_global'),
                         params=dict(title=old_title,
                                  realm=old_realm,
                                  ga_code=new_ga_code,
@@ -168,7 +168,7 @@
         self.checkSessionFlash(response, 'Updated application settings')
         assert Setting.get_app_settings()['captcha_private_key'] == '1234567890'
 
-        response = self.app.get(url('register'))
+        response = self.app.get(base.url('register'))
         response.mustcontain('captcha')
 
     def test_captcha_deactivate(self):
@@ -176,7 +176,7 @@
         old_title = 'Kallithea'
         old_realm = 'Kallithea authentication'
         new_ga_code = ''
-        response = self.app.post(url('admin_settings_global'),
+        response = self.app.post(base.url('admin_settings_global'),
                         params=dict(title=old_title,
                                  realm=old_realm,
                                  ga_code=new_ga_code,
@@ -188,7 +188,7 @@
         self.checkSessionFlash(response, 'Updated application settings')
         assert Setting.get_app_settings()['captcha_private_key'] == ''
 
-        response = self.app.get(url('register'))
+        response = self.app.get(base.url('register'))
         response.mustcontain(no=['captcha'])
 
     def test_title_change(self):
@@ -198,7 +198,7 @@
         old_realm = 'Kallithea authentication'
 
         for new_title in ['Changed', 'Żółwik', old_title]:
-            response = self.app.post(url('admin_settings_global'),
+            response = self.app.post(base.url('admin_settings_global'),
                         params=dict(title=new_title,
                                  realm=old_realm,
                                  ga_code='',
@@ -208,7 +208,7 @@
                                 ))
 
             self.checkSessionFlash(response, 'Updated application settings')
-            assert Setting.get_app_settings()['title'] == new_title.decode('utf-8')
+            assert Setting.get_app_settings()['title'] == new_title
 
             response = response.follow()
             response.mustcontain("""<span class="branding">%s</span>""" % new_title)
--- a/kallithea/tests/functional/test_admin_user_groups.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_user_groups.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,23 +1,23 @@
 # -*- coding: utf-8 -*-
 from kallithea.model.db import Permission, UserGroup, UserGroupToPerm
 from kallithea.model.meta import Session
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
 TEST_USER_GROUP = u'admins_test'
 
 
-class TestAdminUsersGroupsController(TestController):
+class TestAdminUsersGroupsController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('users_groups'))
+        response = self.app.get(base.url('users_groups'))
         # Test response...
 
     def test_create(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP
-        response = self.app.post(url('users_groups'),
+        response = self.app.post(base.url('users_groups'),
                                  {'users_group_name': users_group_name,
                                   'user_group_description': u'DESC',
                                   'active': True,
@@ -30,19 +30,19 @@
                                '/edit">%s</a>' % TEST_USER_GROUP)
 
     def test_new(self):
-        response = self.app.get(url('new_users_group'))
+        response = self.app.get(base.url('new_users_group'))
 
     def test_update(self):
-        response = self.app.post(url('update_users_group', id=1), status=403)
+        response = self.app.post(base.url('update_users_group', id=1), status=403)
 
     def test_update_browser_fakeout(self):
-        response = self.app.post(url('update_users_group', id=1),
+        response = self.app.post(base.url('update_users_group', id=1),
                                  params=dict(_session_csrf_secret_token=self.session_csrf_secret_token()))
 
     def test_delete(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP + 'another'
-        response = self.app.post(url('users_groups'),
+        response = self.app.post(base.url('users_groups'),
                                  {'users_group_name': users_group_name,
                                   'user_group_description': u'DESC',
                                   'active': True,
@@ -55,7 +55,7 @@
         gr = Session().query(UserGroup) \
             .filter(UserGroup.users_group_name == users_group_name).one()
 
-        response = self.app.post(url('delete_users_group', id=gr.users_group_id),
+        response = self.app.post(base.url('delete_users_group', id=gr.users_group_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         gr = Session().query(UserGroup) \
@@ -66,7 +66,7 @@
     def test_default_perms_enable_repository_read_on_group(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP + 'another2'
-        response = self.app.post(url('users_groups'),
+        response = self.app.post(base.url('users_groups'),
                                  {'users_group_name': users_group_name,
                                   'user_group_description': u'DESC',
                                   'active': True,
@@ -77,7 +77,7 @@
         self.checkSessionFlash(response,
                                'Created user group ')
         ## ENABLE REPO CREATE ON A GROUP
-        response = self.app.post(url('edit_user_group_default_perms_update',
+        response = self.app.post(base.url('edit_user_group_default_perms_update',
                                      id=ug.users_group_id),
                                  {'create_repo_perm': True,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -97,7 +97,7 @@
 
         ## DISABLE REPO CREATE ON A GROUP
         response = self.app.post(
-            url('edit_user_group_default_perms_update', id=ug.users_group_id),
+            base.url('edit_user_group_default_perms_update', id=ug.users_group_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         response.follow()
@@ -118,7 +118,7 @@
         # DELETE !
         ug = UserGroup.get_by_group_name(users_group_name)
         ugid = ug.users_group_id
-        response = self.app.post(url('delete_users_group', id=ug.users_group_id),
+        response = self.app.post(base.url('delete_users_group', id=ug.users_group_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         response = response.follow()
         gr = Session().query(UserGroup) \
@@ -135,7 +135,7 @@
     def test_default_perms_enable_repository_fork_on_group(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP + 'another2'
-        response = self.app.post(url('users_groups'),
+        response = self.app.post(base.url('users_groups'),
                                  {'users_group_name': users_group_name,
                                   'user_group_description': u'DESC',
                                   'active': True,
@@ -146,7 +146,7 @@
         self.checkSessionFlash(response,
                                'Created user group ')
         ## ENABLE REPO CREATE ON A GROUP
-        response = self.app.post(url('edit_user_group_default_perms_update',
+        response = self.app.post(base.url('edit_user_group_default_perms_update',
                                      id=ug.users_group_id),
                                  {'fork_repo_perm': True, '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
@@ -165,7 +165,7 @@
                     [ug.users_group_id, p3.permission_id]])
 
         ## DISABLE REPO CREATE ON A GROUP
-        response = self.app.post(url('edit_user_group_default_perms_update', id=ug.users_group_id),
+        response = self.app.post(base.url('edit_user_group_default_perms_update', id=ug.users_group_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         response.follow()
@@ -185,7 +185,7 @@
         # DELETE !
         ug = UserGroup.get_by_group_name(users_group_name)
         ugid = ug.users_group_id
-        response = self.app.post(url('delete_users_group', id=ug.users_group_id),
+        response = self.app.post(base.url('delete_users_group', id=ug.users_group_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         response = response.follow()
         gr = Session().query(UserGroup) \
@@ -201,5 +201,5 @@
         assert perms == []
 
     def test_delete_browser_fakeout(self):
-        response = self.app.post(url('delete_users_group', id=1),
+        response = self.app.post(base.url('delete_users_group', id=1),
                                  params=dict(_session_csrf_secret_token=self.session_csrf_secret_token()))
--- a/kallithea/tests/functional/test_admin_users.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_admin_users.py	Thu Feb 06 01:19:23 2020 +0100
@@ -24,7 +24,7 @@
 from kallithea.model.db import Permission, RepoGroup, User, UserApiKeys, UserSshKeys
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -43,7 +43,7 @@
         fixture.destroy_repo_group(repo_group)
 
 
-class TestAdminUsersController(TestController):
+class TestAdminUsersController(base.TestController):
     test_user_1 = 'testme'
 
     @classmethod
@@ -54,7 +54,7 @@
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('users'))
+        response = self.app.get(base.url('users'))
         # TODO: Test response...
 
     def test_create(self):
@@ -66,7 +66,7 @@
         lastname = u'lastname'
         email = 'mail@example.com'
 
-        response = self.app.post(url('new_user'),
+        response = self.app.post(base.url('new_user'),
             {'username': username,
              'password': password,
              'password_confirmation': password_confirmation,
@@ -102,7 +102,7 @@
         lastname = u'lastname'
         email = 'errmail.example.com'
 
-        response = self.app.post(url('new_user'),
+        response = self.app.post(base.url('new_user'),
             {'username': username,
              'password': password,
              'name': name,
@@ -126,9 +126,9 @@
 
     def test_new(self):
         self.log_user()
-        response = self.app.get(url('new_user'))
+        response = self.app.get(base.url('new_user'))
 
-    @parametrize('name,attrs',
+    @base.parametrize('name,attrs',
         [('firstname', {'firstname': 'new_username'}),
          ('lastname', {'lastname': 'new_username'}),
          ('admin', {'admin': True}),
@@ -167,7 +167,7 @@
             # not filled so we use creation data
 
         params.update({'_session_csrf_secret_token': self.session_csrf_secret_token()})
-        response = self.app.post(url('update_user', id=usr.user_id), params)
+        response = self.app.post(base.url('update_user', id=usr.user_id), params)
         self.checkSessionFlash(response, 'User updated successfully')
         params.pop('_session_csrf_secret_token')
 
@@ -186,7 +186,7 @@
 
         new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         self.checkSessionFlash(response, 'Successfully deleted user')
@@ -201,18 +201,18 @@
 
         new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
-        self.checkSessionFlash(response, 'User "%s" still '
+        self.checkSessionFlash(response, 'User &quot;%s&quot; still '
                                'owns 1 repositories and cannot be removed. '
                                'Switch owners or remove those repositories: '
                                '%s' % (username, reponame))
 
-        response = self.app.post(url('delete_repo', repo_name=reponame),
+        response = self.app.post(base.url('delete_repo', repo_name=reponame),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Deleted repository %s' % reponame)
 
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Successfully deleted user')
 
@@ -223,22 +223,22 @@
 
         self.log_user()
 
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
-        self.checkSessionFlash(response, 'User "%s" still '
+        self.checkSessionFlash(response, 'User &quot;%s&quot; still '
                                'owns 1 repository groups and cannot be removed. '
                                'Switch owners or remove those repository groups: '
                                '%s' % (username, groupname))
 
         # Relevant _if_ the user deletion succeeded to make sure we can render groups without owner
         # rg = RepoGroup.get_by_group_name(group_name=groupname)
-        # response = self.app.get(url('repos_groups', id=rg.group_id))
+        # response = self.app.get(base.url('repos_groups', id=rg.group_id))
 
-        response = self.app.post(url('delete_repo_group', group_name=groupname),
+        response = self.app.post(base.url('delete_repo_group', group_name=groupname),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Removed repository group %s' % groupname)
 
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Successfully deleted user')
 
@@ -252,27 +252,27 @@
 
         new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
-        self.checkSessionFlash(response, 'User "%s" still '
+        self.checkSessionFlash(response, 'User &quot;%s&quot; still '
                                'owns 1 user groups and cannot be removed. '
                                'Switch owners or remove those user groups: '
                                '%s' % (username, groupname))
 
         # TODO: why do this fail?
-        #response = self.app.delete(url('delete_users_group', id=groupname))
+        #response = self.app.delete(base.url('delete_users_group', id=groupname))
         #self.checkSessionFlash(response, 'Removed user group %s' % groupname)
 
         fixture.destroy_user_group(ug.users_group_id)
 
-        response = self.app.post(url('delete_user', id=new_user.user_id),
+        response = self.app.post(base.url('delete_user', id=new_user.user_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Successfully deleted user')
 
     def test_edit(self):
         self.log_user()
-        user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
-        response = self.app.get(url('edit_user', id=user.user_id))
+        user = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
+        response = self.app.get(base.url('edit_user', id=user.user_id))
 
     def test_add_perm_create_repo(self):
         self.log_user()
@@ -290,7 +290,7 @@
             assert UserModel().has_perm(user, perm_none) == False
             assert UserModel().has_perm(user, perm_create) == False
 
-            response = self.app.post(url('edit_user_perms_update', id=uid),
+            response = self.app.post(base.url('edit_user_perms_update', id=uid),
                                      params=dict(create_repo_perm=True,
                                                  _session_csrf_secret_token=self.session_csrf_secret_token()))
 
@@ -320,7 +320,7 @@
             assert UserModel().has_perm(user, perm_none) == False
             assert UserModel().has_perm(user, perm_create) == False
 
-            response = self.app.post(url('edit_user_perms_update', id=uid),
+            response = self.app.post(base.url('edit_user_perms_update', id=uid),
                                      params=dict(_session_csrf_secret_token=self.session_csrf_secret_token()))
 
             perm_none = Permission.get_by_key('hg.create.none')
@@ -349,7 +349,7 @@
             assert UserModel().has_perm(user, perm_none) == False
             assert UserModel().has_perm(user, perm_fork) == False
 
-            response = self.app.post(url('edit_user_perms_update', id=uid),
+            response = self.app.post(base.url('edit_user_perms_update', id=uid),
                                      params=dict(create_repo_perm=True,
                                                  _session_csrf_secret_token=self.session_csrf_secret_token()))
 
@@ -379,7 +379,7 @@
             assert UserModel().has_perm(user, perm_none) == False
             assert UserModel().has_perm(user, perm_fork) == False
 
-            response = self.app.post(url('edit_user_perms_update', id=uid),
+            response = self.app.post(base.url('edit_user_perms_update', id=uid),
                                      params=dict(_session_csrf_secret_token=self.session_csrf_secret_token()))
 
             perm_none = Permission.get_by_key('hg.create.none')
@@ -394,11 +394,11 @@
 
     def test_ips(self):
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
-        response = self.app.get(url('edit_user_ips', id=user.user_id))
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
+        response = self.app.get(base.url('edit_user_ips', id=user.user_id))
         response.mustcontain('All IP addresses are allowed')
 
-    @parametrize('test_name,ip,ip_range,failure', [
+    @base.parametrize('test_name,ip,ip_range,failure', [
         ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
         ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
         ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
@@ -408,26 +408,26 @@
     ])
     def test_add_ip(self, test_name, ip, ip_range, failure, auto_clear_ip_permissions):
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_ips_update', id=user_id),
+        response = self.app.post(base.url('edit_user_ips_update', id=user_id),
                                  params=dict(new_ip=ip, _session_csrf_secret_token=self.session_csrf_secret_token()))
 
         if failure:
             self.checkSessionFlash(response, 'Please enter a valid IPv4 or IPv6 address')
-            response = self.app.get(url('edit_user_ips', id=user_id))
+            response = self.app.get(base.url('edit_user_ips', id=user_id))
             response.mustcontain(no=[ip])
             response.mustcontain(no=[ip_range])
 
         else:
-            response = self.app.get(url('edit_user_ips', id=user_id))
+            response = self.app.get(base.url('edit_user_ips', id=user_id))
             response.mustcontain(ip)
             response.mustcontain(ip_range)
 
     def test_delete_ip(self, auto_clear_ip_permissions):
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
         ip = '127.0.0.1/32'
         ip_range = '127.0.0.1 - 127.0.0.1'
@@ -436,14 +436,14 @@
             Session().commit()
         new_ip_id = new_ip.ip_id
 
-        response = self.app.get(url('edit_user_ips', id=user_id))
+        response = self.app.get(base.url('edit_user_ips', id=user_id))
         response.mustcontain(ip)
         response.mustcontain(ip_range)
 
-        self.app.post(url('edit_user_ips_delete', id=user_id),
+        self.app.post(base.url('edit_user_ips_delete', id=user_id),
                       params=dict(del_ip_id=new_ip_id, _session_csrf_secret_token=self.session_csrf_secret_token()))
 
-        response = self.app.get(url('edit_user_ips', id=user_id))
+        response = self.app.get(base.url('edit_user_ips', id=user_id))
         response.mustcontain('All IP addresses are allowed')
         response.mustcontain(no=[ip])
         response.mustcontain(no=[ip_range])
@@ -451,22 +451,22 @@
     def test_api_keys(self):
         self.log_user()
 
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
-        response = self.app.get(url('edit_user_api_keys', id=user.user_id))
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
+        response = self.app.get(base.url('edit_user_api_keys', id=user.user_id))
         response.mustcontain(user.api_key)
         response.mustcontain('Expires: Never')
 
-    @parametrize('desc,lifetime', [
+    @base.parametrize('desc,lifetime', [
         ('forever', -1),
         ('5mins', 60*5),
         ('30days', 60*60*24*30),
     ])
     def test_add_api_keys(self, desc, lifetime):
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_api_keys_update', id=user_id),
+        response = self.app.post(base.url('edit_user_api_keys_update', id=user_id),
                  {'description': desc, 'lifetime': lifetime, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully created')
         try:
@@ -481,10 +481,10 @@
 
     def test_remove_api_key(self):
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_api_keys_update', id=user_id),
+        response = self.app.post(base.url('edit_user_api_keys_update', id=user_id),
                 {'description': 'desc', 'lifetime': -1, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully created')
         response = response.follow()
@@ -493,7 +493,7 @@
         keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
         assert 1 == len(keys)
 
-        response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
+        response = self.app.post(base.url('edit_user_api_keys_delete', id=user_id),
                  {'del_api_key': keys[0].api_key, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully deleted')
         keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
@@ -501,14 +501,14 @@
 
     def test_reset_main_api_key(self):
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
         api_key = user.api_key
-        response = self.app.get(url('edit_user_api_keys', id=user_id))
+        response = self.app.get(base.url('edit_user_api_keys', id=user_id))
         response.mustcontain(api_key)
         response.mustcontain('Expires: Never')
 
-        response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
+        response = self.app.post(base.url('edit_user_api_keys_delete', id=user_id),
                  {'del_api_key_builtin': api_key, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully reset')
         response = response.follow()
@@ -520,10 +520,10 @@
         fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8'
 
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_ssh_keys', id=user_id),
+        response = self.app.post(base.url('edit_user_ssh_keys', id=user_id),
                                  {'description': description,
                                   'public_key': public_key,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -543,10 +543,10 @@
         fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8'
 
         self.log_user()
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_ssh_keys', id=user_id),
+        response = self.app.post(base.url('edit_user_ssh_keys', id=user_id),
                                  {'description': description,
                                   'public_key': public_key,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -555,15 +555,15 @@
         ssh_key = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).one()
         assert ssh_key.description == u'me@localhost'
 
-        response = self.app.post(url('edit_user_ssh_keys_delete', id=user_id),
-                                 {'del_public_key': ssh_key.public_key,
+        response = self.app.post(base.url('edit_user_ssh_keys_delete', id=user_id),
+                                 {'del_public_key_fingerprint': ssh_key.fingerprint,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'SSH key successfully deleted')
         keys = UserSshKeys.query().all()
         assert 0 == len(keys)
 
 
-class TestAdminUsersController_unittest(TestController):
+class TestAdminUsersController_unittest(base.TestController):
     """ Unit tests for the users controller """
 
     def test_get_user_or_raise_if_default(self, monkeypatch, test_context_fixture):
@@ -574,14 +574,14 @@
 
         u = UsersController()
         # a regular user should work correctly
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.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):
+class TestAdminUsersControllerForDefaultUser(base.TestController):
     """
     Edit actions on the default user are not allowed.
     Validate that they throw a 404 exception.
@@ -589,59 +589,59 @@
     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)
+        response = self.app.get(base.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)
+        response = self.app.get(base.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)
+        response = self.app.get(base.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_update', id=user.user_id),
+        response = self.app.post(base.url('edit_user_api_keys_update', id=user.user_id),
                  {'_session_csrf_secret_token': self.session_csrf_secret_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_delete', id=user.user_id),
+        response = self.app.post(base.url('edit_user_api_keys_delete', id=user.user_id),
                  {'_session_csrf_secret_token': self.session_csrf_secret_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)
+        response = self.app.get(base.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_update', id=user.user_id),
+        response = self.app.post(base.url('edit_user_perms_update', id=user.user_id),
                  {'_session_csrf_secret_token': self.session_csrf_secret_token()}, status=404)
 
     # Emails
     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)
+        response = self.app.get(base.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_update', id=user.user_id),
+        response = self.app.post(base.url('edit_user_emails_update', id=user.user_id),
                  {'_session_csrf_secret_token': self.session_csrf_secret_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_delete', id=user.user_id),
+        response = self.app.post(base.url('edit_user_emails_delete', id=user.user_id),
                  {'_session_csrf_secret_token': self.session_csrf_secret_token()}, status=404)
 
     # IP addresses
@@ -650,4 +650,4 @@
     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)
+        response = self.app.get(base.url('edit_user_ips', id=user.user_id), status=404)
--- a/kallithea/tests/functional/test_changelog.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_changelog.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,12 +1,12 @@
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestChangelogController(TestController):
+class TestChangelogController(base.TestController):
 
     def test_index_hg(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO))
+        response = self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO))
 
         response.mustcontain('''id="chg_20" class="mergerow"''')
         response.mustcontain(
@@ -17,7 +17,7 @@
         )
         # rev 640: code garden
         response.mustcontain(
-            """<a class="changeset_hash" href="/%s/changeset/0a4e54a4460401d6dbbd6a3604b17cd2b3606b82">r640:0a4e54a44604</a>""" % HG_REPO
+            """<a class="changeset_hash" href="/%s/changeset/0a4e54a4460401d6dbbd6a3604b17cd2b3606b82">r640:0a4e54a44604</a>""" % base.HG_REPO
         )
         response.mustcontain("""code garden""")
 
@@ -26,18 +26,18 @@
     def test_index_pagination_hg(self):
         self.log_user()
         # pagination
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO), {'page': 1})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO), {'page': 2})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO), {'page': 3})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO), {'page': 4})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO), {'page': 5})
-        response = self.app.get(url(controller='changelog', action='index',
-                                    repo_name=HG_REPO), {'page': 6, 'size': 20})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO), {'page': 1})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO), {'page': 2})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO), {'page': 3})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO), {'page': 4})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO), {'page': 5})
+        response = self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.HG_REPO), {'page': 6, 'size': 20})
 
         # Test response after pagination...
         response.mustcontain(
@@ -53,8 +53,8 @@
 
     def test_index_git(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO))
+        response = self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO))
 
         response.mustcontain('''id="chg_20" class=""''') # why no mergerow for git?
         response.mustcontain(
@@ -82,18 +82,18 @@
     def test_index_pagination_git(self):
         self.log_user()
         # pagination
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO), {'page': 1})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO), {'page': 2})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO), {'page': 3})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO), {'page': 4})
-        self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO), {'page': 5})
-        response = self.app.get(url(controller='changelog', action='index',
-                                    repo_name=GIT_REPO), {'page': 6, 'size': 20})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO), {'page': 1})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO), {'page': 2})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO), {'page': 3})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO), {'page': 4})
+        self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO), {'page': 5})
+        response = self.app.get(base.url(controller='changelog', action='index',
+                                    repo_name=base.GIT_REPO), {'page': 6, 'size': 20})
 
         # Test response after pagination...
         response.mustcontain(
@@ -109,9 +109,9 @@
 
     def test_index_hg_with_filenode(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
+        response = self.app.get(base.url(controller='changelog', action='index',
                                     revision='tip', f_path='/vcs/exceptions.py',
-                                    repo_name=HG_REPO))
+                                    repo_name=base.HG_REPO))
         # history commits messages
         response.mustcontain('Added exceptions module, this time for real')
         response.mustcontain('Added not implemented hg backend test case')
@@ -120,9 +120,9 @@
 
     def test_index_git_with_filenode(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
+        response = self.app.get(base.url(controller='changelog', action='index',
                                     revision='tip', f_path='/vcs/exceptions.py',
-                                    repo_name=GIT_REPO))
+                                    repo_name=base.GIT_REPO))
         # history commits messages
         response.mustcontain('Added exceptions module, this time for real')
         response.mustcontain('Added not implemented hg backend test case')
@@ -130,28 +130,28 @@
 
     def test_index_hg_with_filenode_that_is_dirnode(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
+        response = self.app.get(base.url(controller='changelog', action='index',
                                     revision='tip', f_path='/tests',
-                                    repo_name=HG_REPO))
+                                    repo_name=base.HG_REPO))
         assert response.status == '302 Found'
 
     def test_index_git_with_filenode_that_is_dirnode(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
+        response = self.app.get(base.url(controller='changelog', action='index',
                                     revision='tip', f_path='/tests',
-                                    repo_name=GIT_REPO))
+                                    repo_name=base.GIT_REPO))
         assert response.status == '302 Found'
 
     def test_index_hg_with_filenode_not_existing(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
+        response = self.app.get(base.url(controller='changelog', action='index',
                                     revision='tip', f_path='/wrong_path',
-                                    repo_name=HG_REPO))
+                                    repo_name=base.HG_REPO))
         assert response.status == '302 Found'
 
     def test_index_git_with_filenode_not_existing(self):
         self.log_user()
-        response = self.app.get(url(controller='changelog', action='index',
+        response = self.app.get(base.url(controller='changelog', action='index',
                                     revision='tip', f_path='/wrong_path',
-                                    repo_name=GIT_REPO))
+                                    repo_name=base.GIT_REPO))
         assert response.status == '302 Found'
--- a/kallithea/tests/functional/test_changeset.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_changeset.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,24 +1,24 @@
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestChangesetController(TestController):
+class TestChangesetController(base.TestController):
 
     def test_index(self):
-        response = self.app.get(url(controller='changeset', action='index',
-                                    repo_name=HG_REPO, revision='tip'))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                    repo_name=base.HG_REPO, revision='tip'))
         # Test response...
 
     def test_changeset_range(self):
-        #print self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO))
+        #print self.app.get(base.url(controller='changelog', action='index', repo_name=base.HG_REPO))
 
-        response = self.app.get(url(controller='changeset', action='index',
-                                    repo_name=HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52...96507bd11ecc815ebc6270fdf6db110928c09c1e'))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                    repo_name=base.HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52...96507bd11ecc815ebc6270fdf6db110928c09c1e'))
 
-        response = self.app.get(url(controller='changeset', action='changeset_raw',
-                                    repo_name=HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52'))
+        response = self.app.get(base.url(controller='changeset', action='changeset_raw',
+                                    repo_name=base.HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52'))
 
-        response = self.app.get(url(controller='changeset', action='changeset_patch',
-                                    repo_name=HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52'))
+        response = self.app.get(base.url(controller='changeset', action='changeset_patch',
+                                    repo_name=base.HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52'))
 
-        response = self.app.get(url(controller='changeset', action='changeset_download',
-                                    repo_name=HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52'))
+        response = self.app.get(base.url(controller='changeset', action='changeset_download',
+                                    repo_name=base.HG_REPO, revision='a53d9201d4bc278910d416d94941b7ea007ecd52'))
--- a/kallithea/tests/functional/test_changeset_pullrequests_comments.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_changeset_pullrequests_comments.py	Thu Feb 06 01:19:23 2020 +0100
@@ -3,10 +3,10 @@
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.db import ChangesetComment, PullRequest
 from kallithea.model.meta import Session
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestChangeSetCommentsController(TestController):
+class TestChangeSetCommentsController(base.TestController):
 
     def setup_method(self, method):
         for x in ChangesetComment.query().all():
@@ -19,14 +19,14 @@
         text = u'general comment on changeset'
 
         params = {'text': text, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
+        response = self.app.post(base.url(controller='changeset', action='comment',
+                                     repo_name=base.HG_REPO, revision=rev),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                repo_name=base.HG_REPO, revision=rev))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 1 comment (0 inline, 1 general)'''
@@ -44,14 +44,14 @@
         line = 'n1'
 
         params = {'text': text, 'f_path': f_path, 'line': line, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
+        response = self.app.post(base.url(controller='changeset', action='comment',
+                                     repo_name=base.HG_REPO, revision=rev),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                repo_name=base.HG_REPO, revision=rev))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 1 comment (1 inline, 0 general)'''
@@ -70,22 +70,22 @@
         self.log_user()
 
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
-        text = u'@%s check CommentOnRevision' % TEST_USER_REGULAR_LOGIN
+        text = u'@%s check CommentOnRevision' % base.TEST_USER_REGULAR_LOGIN
 
         params = {'text': text, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
+        response = self.app.post(base.url(controller='changeset', action='comment',
+                                     repo_name=base.HG_REPO, revision=rev),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                repo_name=base.HG_REPO, revision=rev))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 1 comment (0 inline, 1 general)'''
         )
-        response.mustcontain('<b>@%s</b> check CommentOnRevision' % TEST_USER_REGULAR_LOGIN)
+        response.mustcontain('<b>@%s</b> check CommentOnRevision' % base.TEST_USER_REGULAR_LOGIN)
 
         # test DB
         assert ChangesetComment.query().count() == 1
@@ -97,14 +97,14 @@
 
         params = {'text': text, 'changeset_status': 'rejected',
                 '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
+        response = self.app.post(base.url(controller='changeset', action='comment',
+                                     repo_name=base.HG_REPO, revision=rev),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                repo_name=base.HG_REPO, revision=rev))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 1 comment (0 inline, 1 general)'''
@@ -115,7 +115,7 @@
         assert ChangesetComment.query().count() == 1
 
         # check status
-        status = ChangesetStatusModel().get_status(repo=HG_REPO, revision=rev)
+        status = ChangesetStatusModel().get_status(repo=base.HG_REPO, revision=rev)
         assert status == 'rejected'
 
     def test_delete(self):
@@ -124,24 +124,24 @@
         text = u'general comment on changeset to be deleted'
 
         params = {'text': text, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
+        response = self.app.post(base.url(controller='changeset', action='comment',
+                                     repo_name=base.HG_REPO, revision=rev),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
 
         comments = ChangesetComment.query().all()
         assert len(comments) == 1
         comment_id = comments[0].comment_id
 
-        self.app.post(url("changeset_comment_delete",
-                                    repo_name=HG_REPO,
+        self.app.post(base.url("changeset_comment_delete",
+                                    repo_name=base.HG_REPO,
                                     comment_id=comment_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         comments = ChangesetComment.query().all()
         assert len(comments) == 0
 
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
+        response = self.app.get(base.url(controller='changeset', action='index',
+                                repo_name=base.HG_REPO, revision=rev))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 0 comments (0 inline, 0 general)'''
@@ -149,7 +149,7 @@
         response.mustcontain(no=text)
 
 
-class TestPullrequestsCommentsController(TestController):
+class TestPullrequestsCommentsController(base.TestController):
 
     def setup_method(self, method):
         for x in ChangesetComment.query().all():
@@ -157,18 +157,18 @@
         Session().commit()
 
     def _create_pr(self):
-        response = self.app.post(url(controller='pullrequests', action='create',
-                                     repo_name=HG_REPO),
-                                 {'org_repo': HG_REPO,
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
+                                 {'org_repo': base.HG_REPO,
                                   'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                  },
                                  status=302)
-        pr_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr_id = int(re.search(r'/pull-request/(\d+)/', response.location).group(1))
         return pr_id
 
     def test_create(self):
@@ -177,14 +177,14 @@
 
         text = u'general comment on pullrequest'
         params = {'text': text, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''))
         # PRs currently always have an initial 'Under Review' status change
         # that counts as a general comment, hence '2' in the test below. That
         # could be counted as a misfeature, to be reworked later.
@@ -205,14 +205,14 @@
         f_path = 'vcs/web/simplevcs/views/repository.py'
         line = 'n1'
         params = {'text': text, 'f_path': f_path, 'line': line, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 2 comments (1 inline, 1 general)'''
@@ -231,21 +231,21 @@
         self.log_user()
         pr_id = self._create_pr()
 
-        text = u'@%s check CommentOnRevision' % TEST_USER_REGULAR_LOGIN
+        text = u'@%s check CommentOnRevision' % base.TEST_USER_REGULAR_LOGIN
         params = {'text': text, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 2 comments (0 inline, 2 general)'''
         )
-        response.mustcontain('<b>@%s</b> check CommentOnRevision' % TEST_USER_REGULAR_LOGIN)
+        response.mustcontain('<b>@%s</b> check CommentOnRevision' % base.TEST_USER_REGULAR_LOGIN)
 
         # test DB
         assert ChangesetComment.query().count() == 2
@@ -257,14 +257,14 @@
         text = u'general comment on pullrequest'
         params = {'text': text, 'changeset_status': 'rejected',
                 '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''))
         # PRs currently always have an initial 'Under Review' status change
         # that counts as a general comment, hence '2' in the test below. That
         # could be counted as a misfeature, to be reworked later.
@@ -278,7 +278,7 @@
         assert ChangesetComment.query().count() == 2
 
         # check status
-        status = ChangesetStatusModel().get_status(repo=HG_REPO, pull_request=pr_id)
+        status = ChangesetStatusModel().get_status(repo=base.HG_REPO, pull_request=pr_id)
         assert status == 'rejected'
 
     def test_delete(self):
@@ -287,24 +287,24 @@
 
         text = u'general comment on changeset to be deleted'
         params = {'text': text, '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
 
         comments = ChangesetComment.query().all()
         assert len(comments) == 2
         comment_id = comments[-1].comment_id
 
-        self.app.post(url("pullrequest_comment_delete",
-                                    repo_name=HG_REPO,
+        self.app.post(base.url("pullrequest_comment_delete",
+                                    repo_name=base.HG_REPO,
                                     comment_id=comment_id),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         comments = ChangesetComment.query().all()
         assert len(comments) == 1
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''))
         response.mustcontain(
             '''<div class="comments-number">'''
             ''' 1 comment (0 inline, 1 general)'''
@@ -318,14 +318,14 @@
         text = u'general comment on pullrequest'
         params = {'text': text, 'save_close': 'close',
                 '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''))
         response.mustcontain(
             '''title (Closed)'''
         )
@@ -341,14 +341,14 @@
         text = u'general comment on pullrequest'
         params = {'text': text, 'save_delete': 'delete',
                 '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         # Test response...
         assert response.status == '200 OK'
 
-        response = self.app.get(url(controller='pullrequests', action='show',
-                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''), status=404)
+        response = self.app.get(base.url(controller='pullrequests', action='show',
+                                repo_name=base.HG_REPO, pull_request_id=pr_id, extra=''), status=404)
 
         # test DB
         assert PullRequest.get(pr_id) is None
@@ -361,16 +361,16 @@
         text = u'general comment on pullrequest'
         params = {'text': text, 'save_close': 'close',
                 '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
         assert response.status == '200 OK'
 
         # attempt delete, should fail
         params = {'text': text, 'save_delete': 'delete',
                 '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        response = self.app.post(url(controller='pullrequests', action='comment',
-                                     repo_name=HG_REPO, pull_request_id=pr_id),
+        response = self.app.post(base.url(controller='pullrequests', action='comment',
+                                     repo_name=base.HG_REPO, pull_request_id=pr_id),
                                      params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'}, status=403)
 
         # verify that PR still exists, in closed state
--- a/kallithea/tests/functional/test_compare.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_compare.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -12,7 +12,7 @@
     return '''<div class="message-firstline"><a class="message-link" href="/%s/changeset/%s">%s</a></div>''' % (repo_name, sha, msg)
 
 
-class TestCompareController(TestController):
+class TestCompareController(base.TestController):
 
     def setup_method(self, method):
         self.r1_id = None
@@ -30,7 +30,7 @@
         self.log_user()
         repo1 = fixture.create_repo(u'one', repo_type='hg',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
         # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
@@ -53,7 +53,7 @@
         rev1 = 'default'
         rev2 = 'default'
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev2,
@@ -81,7 +81,7 @@
         self.log_user()
         repo1 = fixture.create_repo(u'one-git', repo_type='git',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
         # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
@@ -104,7 +104,7 @@
         rev1 = 'master'
         rev2 = 'master'
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev2,
@@ -133,7 +133,7 @@
 
         repo1 = fixture.create_repo(u'one', repo_type='hg',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
 
         self.r1_id = repo1.repo_id
 
@@ -163,7 +163,7 @@
         rev1 = 'default'
         rev2 = 'default'
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev2,
@@ -192,7 +192,7 @@
 
         repo1 = fixture.create_repo(u'one-git', repo_type='git',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
 
         self.r1_id = repo1.repo_id
 
@@ -222,7 +222,7 @@
         rev1 = 'master'
         rev2 = 'master'
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev2,
@@ -263,7 +263,7 @@
 
         repo1 = fixture.create_repo(u'repo1', repo_type='hg',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
 
         # commit something !
@@ -290,7 +290,7 @@
                 content='line1\nline2\nline3\nline4\nline5\nline6\n',
                 message='commit6', vcs_type='hg', parent=cs4)
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=repo2.repo_name,
                                     org_ref_type="rev",
                                     org_ref_name=cs1.short_id,  # parent of cs2, in repo2
@@ -331,7 +331,7 @@
         self.log_user()
         repo1 = fixture.create_repo(u'repo1', repo_type='hg',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
 
         # commit something !
@@ -358,7 +358,7 @@
                 content='line1\nline2\nline3\nline4\nline5\nline6\n',
                 message='commit6', vcs_type='hg', parent=cs4)
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=repo1.repo_name,
                                     org_ref_type="rev",
                                     org_ref_name=cs2.short_id, # parent of cs3, not in repo2
@@ -388,27 +388,27 @@
     def test_compare_remote_branches_hg(self):
         self.log_user()
 
-        repo2 = fixture.create_fork(HG_REPO, HG_FORK)
+        repo2 = fixture.create_fork(base.HG_REPO, base.HG_FORK)
         self.r2_id = repo2.repo_id
         rev1 = '56349e29c2af'
         rev2 = '7d4bc8ec6be5'
 
-        response = self.app.get(url('compare_url',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.HG_REPO,
                                     org_ref_type="rev",
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
                                     other_ref_name=rev2,
-                                    other_repo=HG_FORK,
+                                    other_repo=base.HG_FORK,
                                     merge='1',))
 
-        response.mustcontain('%s@%s' % (HG_REPO, rev1))
-        response.mustcontain('%s@%s' % (HG_FORK, rev2))
+        response.mustcontain('%s@%s' % (base.HG_REPO, rev1))
+        response.mustcontain('%s@%s' % (base.HG_FORK, rev2))
         ## outgoing changesets between those revisions
 
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_FORK))
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (base.HG_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (base.HG_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (base.HG_FORK, rev2))
 
         ## files
         response.mustcontain("""<a href="#C--9c390eb52cd6">vcs/backends/hg.py</a>""")
@@ -418,27 +418,27 @@
     def test_compare_remote_branches_git(self):
         self.log_user()
 
-        repo2 = fixture.create_fork(GIT_REPO, GIT_FORK)
+        repo2 = fixture.create_fork(base.GIT_REPO, base.GIT_FORK)
         self.r2_id = repo2.repo_id
         rev1 = '102607b09cdd60e2793929c4f90478be29f85a17'
         rev2 = 'd7e0d30fbcae12c90680eb095a4f5f02505ce501'
 
-        response = self.app.get(url('compare_url',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.GIT_REPO,
                                     org_ref_type="rev",
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
                                     other_ref_name=rev2,
-                                    other_repo=GIT_FORK,
+                                    other_repo=base.GIT_FORK,
                                     merge='1',))
 
-        response.mustcontain('%s@%s' % (GIT_REPO, rev1))
-        response.mustcontain('%s@%s' % (GIT_FORK, rev2))
+        response.mustcontain('%s@%s' % (base.GIT_REPO, rev1))
+        response.mustcontain('%s@%s' % (base.GIT_FORK, rev2))
         ## outgoing changesets between those revisions
 
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/49d3fd156b6f7db46313fac355dca1a0b94a0017">r4:49d3fd156b6f</a>""" % (GIT_FORK))
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/2d1028c054665b962fa3d307adfc923ddd528038">r5:2d1028c05466</a>""" % (GIT_FORK))
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/d7e0d30fbcae12c90680eb095a4f5f02505ce501">r6:%s</a>""" % (GIT_FORK, rev2[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/49d3fd156b6f7db46313fac355dca1a0b94a0017">r4:49d3fd156b6f</a>""" % (base.GIT_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/2d1028c054665b962fa3d307adfc923ddd528038">r5:2d1028c05466</a>""" % (base.GIT_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/d7e0d30fbcae12c90680eb095a4f5f02505ce501">r6:%s</a>""" % (base.GIT_FORK, rev2[:12]))
 
         ## files
         response.mustcontain("""<a href="#C--9c390eb52cd6">vcs/backends/hg.py</a>""")
@@ -450,7 +450,7 @@
 
         repo1 = fixture.create_repo(u'one', repo_type='hg',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
 
         self.r1_id = repo1.repo_id
         r1_name = repo1.repo_name
@@ -461,7 +461,7 @@
         assert repo1.scm_instance.revisions == [cs0.raw_id]
         # fork the repo1
         repo2 = fixture.create_fork(r1_name, u'one-fork',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
         Session().commit()
         assert repo2.scm_instance.revisions == [cs0.raw_id]
         self.r2_id = repo2.repo_id
@@ -482,7 +482,7 @@
         rev1 = 'default'
         rev2 = 'default'
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev2,
@@ -500,7 +500,7 @@
         # compare !
         rev1 = 'default'
         rev2 = 'default'
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev2,
@@ -522,7 +522,7 @@
 
         repo1 = fixture.create_repo(u'one-git', repo_type='git',
                                     repo_description='diff-test',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
 
         self.r1_id = repo1.repo_id
         r1_name = repo1.repo_name
@@ -534,7 +534,7 @@
         assert repo1.scm_instance.revisions == [cs0.raw_id]
         # fork the repo1
         repo2 = fixture.create_fork(r1_name, u'one-git-fork',
-                                    cur_user=TEST_USER_ADMIN_LOGIN)
+                                    cur_user=base.TEST_USER_ADMIN_LOGIN)
         Session().commit()
         assert repo2.scm_instance.revisions == [cs0.raw_id]
         self.r2_id = repo2.repo_id
@@ -556,7 +556,7 @@
         rev1 = 'master'
         rev2 = 'master'
 
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev1,
@@ -574,7 +574,7 @@
         # compare !
         rev1 = 'master'
         rev2 = 'master'
-        response = self.app.get(url('compare_url',
+        response = self.app.get(base.url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
                                     org_ref_name=rev1,
--- a/kallithea/tests/functional/test_compare_local.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_compare_local.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,31 +1,31 @@
 # -*- coding: utf-8 -*-
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestCompareController(TestController):
+class TestCompareController(base.TestController):
 
     def test_compare_tag_hg(self):
         self.log_user()
         tag1 = 'v0.1.2'
         tag2 = 'v0.1.3'
-        response = self.app.get(url('compare_url',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.HG_REPO,
                                     org_ref_type="tag",
                                     org_ref_name=tag1,
                                     other_ref_type="tag",
                                     other_ref_name=tag2,
                                     ), status=200)
-        response.mustcontain('%s@%s' % (HG_REPO, tag1))
-        response.mustcontain('%s@%s' % (HG_REPO, tag2))
+        response.mustcontain('%s@%s' % (base.HG_REPO, tag1))
+        response.mustcontain('%s@%s' % (base.HG_REPO, tag2))
 
         ## outgoing changesets between tags
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % base.HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % base.HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % base.HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % base.HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % base.HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % base.HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % base.HG_REPO)
 
         response.mustcontain('11 files changed with 94 insertions and 64 deletions')
 
@@ -80,24 +80,24 @@
         self.log_user()
         tag1 = 'v0.1.2'
         tag2 = 'v0.1.3'
-        response = self.app.get(url('compare_url',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.GIT_REPO,
                                     org_ref_type="tag",
                                     org_ref_name=tag1,
                                     other_ref_type="tag",
                                     other_ref_name=tag2,
                                     ), status=200)
-        response.mustcontain('%s@%s' % (GIT_REPO, tag1))
-        response.mustcontain('%s@%s' % (GIT_REPO, tag2))
+        response.mustcontain('%s@%s' % (base.GIT_REPO, tag1))
+        response.mustcontain('%s@%s' % (base.GIT_REPO, tag2))
 
         ## outgoing changesets between tags
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % GIT_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % GIT_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % GIT_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % GIT_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % GIT_REPO)
-        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % base.GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % base.GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % base.GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % base.GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % base.GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % base.GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % base.GIT_REPO)
 
         response.mustcontain('11 files changed with 94 insertions and 64 deletions')
 
@@ -116,32 +116,32 @@
 
     def test_index_branch_hg(self):
         self.log_user()
-        response = self.app.get(url('compare_url',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.HG_REPO,
                                     org_ref_type="branch",
                                     org_ref_name='default',
                                     other_ref_type="branch",
                                     other_ref_name='default',
                                     ))
 
-        response.mustcontain('%s@default' % (HG_REPO))
-        response.mustcontain('%s@default' % (HG_REPO))
+        response.mustcontain('%s@default' % (base.HG_REPO))
+        response.mustcontain('%s@default' % (base.HG_REPO))
         # branch are equal
         response.mustcontain('<span class="text-muted">No files</span>')
         response.mustcontain('<span class="text-muted">No changesets</span>')
 
     def test_index_branch_git(self):
         self.log_user()
-        response = self.app.get(url('compare_url',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.GIT_REPO,
                                     org_ref_type="branch",
                                     org_ref_name='master',
                                     other_ref_type="branch",
                                     other_ref_name='master',
                                     ))
 
-        response.mustcontain('%s@master' % (GIT_REPO))
-        response.mustcontain('%s@master' % (GIT_REPO))
+        response.mustcontain('%s@master' % (base.GIT_REPO))
+        response.mustcontain('%s@master' % (base.GIT_REPO))
         # branch are equal
         response.mustcontain('<span class="text-muted">No files</span>')
         response.mustcontain('<span class="text-muted">No changesets</span>')
@@ -151,18 +151,18 @@
         rev1 = 'b986218ba1c9'
         rev2 = '3d8f361e72ab'
 
-        response = self.app.get(url('compare_url',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.HG_REPO,
                                     org_ref_type="rev",
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
                                     other_ref_name=rev2,
                                     ))
-        response.mustcontain('%s@%s' % (HG_REPO, rev1))
-        response.mustcontain('%s@%s' % (HG_REPO, rev2))
+        response.mustcontain('%s@%s' % (base.HG_REPO, rev1))
+        response.mustcontain('%s@%s' % (base.HG_REPO, rev2))
 
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (base.HG_REPO, rev2))
 
         response.mustcontain('1 file changed with 7 insertions and 0 deletions')
         ## files
@@ -173,18 +173,18 @@
         rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
         rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
 
-        response = self.app.get(url('compare_url',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.GIT_REPO,
                                     org_ref_type="rev",
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
                                     other_ref_name=rev2,
                                     ))
-        response.mustcontain('%s@%s' % (GIT_REPO, rev1))
-        response.mustcontain('%s@%s' % (GIT_REPO, rev2))
+        response.mustcontain('%s@%s' % (base.GIT_REPO, rev1))
+        response.mustcontain('%s@%s' % (base.GIT_REPO, rev2))
 
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (base.GIT_REPO, rev2[:12]))
         response.mustcontain('1 file changed with 7 insertions and 0 deletions')
 
         ## files
@@ -195,8 +195,8 @@
         rev1 = 'b986218ba1c9'
         rev2 = '3d8f361e72ab'
 
-        response = self.app.get(url('compare_url',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.HG_REPO,
                                     org_ref_type="rev",
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
@@ -206,18 +206,18 @@
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
 
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (base.HG_REPO, rev2))
 
         response.mustcontain('Merge Ancestor')
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/b986218ba1c9b0d6a259fac9b050b1724ed8e545">%s</a>""" % (HG_REPO, rev1))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/b986218ba1c9b0d6a259fac9b050b1724ed8e545">%s</a>""" % (base.HG_REPO, rev1))
 
     def test_compare_revisions_git_is_ajax_preview(self):
         self.log_user()
         rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
         rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
 
-        response = self.app.get(url('compare_url',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url('compare_url',
+                                    repo_name=base.GIT_REPO,
                                     org_ref_type="rev",
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
@@ -226,7 +226,7 @@
                                     ),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (base.GIT_REPO, rev2[:12]))
 
         response.mustcontain('Merge Ancestor')
-        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/c1214f7e79e02fc37156ff215cd71275450cffc3">%s</a>""" % (GIT_REPO, rev1[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/c1214f7e79e02fc37156ff215cd71275450cffc3">%s</a>""" % (base.GIT_REPO, rev1[:12]))
--- a/kallithea/tests/functional/test_feed.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_feed.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,20 +1,20 @@
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestFeedController(TestController):
+class TestFeedController(base.TestController):
 
     def test_rss(self):
         self.log_user()
-        response = self.app.get(url(controller='feed', action='rss',
-                                    repo_name=HG_REPO))
+        response = self.app.get(base.url(controller='feed', action='rss',
+                                    repo_name=base.HG_REPO))
 
         assert response.content_type == "application/rss+xml"
         assert """<rss version="2.0">""" in response
 
     def test_atom(self):
         self.log_user()
-        response = self.app.get(url(controller='feed', action='atom',
-                                    repo_name=HG_REPO))
+        response = self.app.get(base.url(controller='feed', action='atom',
+                                    repo_name=base.HG_REPO))
 
         assert response.content_type == """application/atom+xml"""
         assert """<?xml version="1.0" encoding="utf-8"?>""" in response
--- a/kallithea/tests/functional/test_files.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_files.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,10 +1,11 @@
 # -*- coding: utf-8 -*-
+import json
 import mimetypes
 import posixpath
 
 from kallithea.model.db import Repository
 from kallithea.model.meta import Session
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -26,51 +27,51 @@
     Session().commit()
 
 
-class TestFilesController(TestController):
+class TestFilesController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='index',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='index',
+                                    repo_name=base.HG_REPO,
                                     revision='tip',
                                     f_path='/'))
         # Test response...
-        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)
+        response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/docs"><i class="icon-folder-open"></i><span>docs</span></a>' % base.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>' % base.HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.gitignore"><i class="icon-doc"></i><span>.gitignore</span></a>' % base.HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgignore"><i class="icon-doc"></i><span>.hgignore</span></a>' % base.HG_REPO)
+        response.mustcontain('<a class="browser-file ypjax-link" href="/%s/files/96507bd11ecc815ebc6270fdf6db110928c09c1e/.hgtags"><i class="icon-doc"></i><span>.hgtags</span></a>' % base.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>' % base.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>' % base.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>' % base.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>' % base.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>' % base.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>' % base.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>' % base.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>' % base.HG_REPO)
 
     def test_index_revision(self):
         self.log_user()
 
         response = self.app.get(
-            url(controller='files', action='index',
-                repo_name=HG_REPO,
+            base.url(controller='files', action='index',
+                repo_name=base.HG_REPO,
                 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
                 f_path='/')
         )
 
         # Test response...
 
-        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('<a class="browser-dir ypjax-link" href="/%s/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs"><i class="icon-folder-open"></i><span>docs</span></a>' % base.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>' % base.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>' % base.HG_REPO)
         response.mustcontain('1.1 KiB')
 
     def test_index_different_branch(self):
         self.log_user()
 
-        response = self.app.get(url(controller='files', action='index',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='index',
+                                    repo_name=base.HG_REPO,
                                     revision='97e8b885c04894463c51898e14387d80c30ed1ee',
                                     f_path='/'))
 
@@ -85,8 +86,8 @@
                   (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
                   (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
 
-            response = self.app.get(url(controller='files', action='index',
-                                    repo_name=HG_REPO,
+            response = self.app.get(base.url(controller='files', action='index',
+                                    repo_name=base.HG_REPO,
                                     revision=r[1],
                                     f_path='/'))
 
@@ -98,8 +99,8 @@
         import kallithea.lib.helpers
         kallithea.lib.helpers._urlify_issues_f = None
         self.log_user()
-        response = self.app.get(url(controller='files', action='index',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='index',
+                                    repo_name=base.HG_REPO,
                                     revision='8911406ad776fdd3d0b9932a2e89677e57405a48',
                                     f_path='vcs/nodes.py'))
 
@@ -114,93 +115,93 @@
 
     def test_file_source_history(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='history',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='history',
+                                    repo_name=base.HG_REPO,
                                     revision='tip',
                                     f_path='vcs/nodes.py'),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
-        assert response.body == HG_NODE_HISTORY
+        assert json.loads(response.body) == json.loads(HG_NODE_HISTORY)
 
     def test_file_source_history_git(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='history',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url(controller='files', action='history',
+                                    repo_name=base.GIT_REPO,
                                     revision='master',
                                     f_path='vcs/nodes.py'),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
-        assert response.body == GIT_NODE_HISTORY
+        assert json.loads(response.body) == json.loads(GIT_NODE_HISTORY)
 
     def test_file_annotation(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='index',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='index',
+                                    repo_name=base.HG_REPO,
                                     revision='tip',
                                     f_path='vcs/nodes.py',
-                                    annotate=True))
+                                    annotate='1'))
 
         response.mustcontain("""r356:25213a5fbb04""")
 
     def test_file_annotation_git(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='index',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url(controller='files', action='index',
+                                    repo_name=base.GIT_REPO,
                                     revision='master',
                                     f_path='vcs/nodes.py',
-                                    annotate=True))
+                                    annotate='1'))
         response.mustcontain("""r345:c994f0de03b2""")
 
     def test_file_annotation_history(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='history',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='history',
+                                    repo_name=base.HG_REPO,
                                     revision='tip',
                                     f_path='vcs/nodes.py',
-                                    annotate=True),
+                                    annotate='1'),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
 
-        assert response.body == HG_NODE_HISTORY
+        assert json.loads(response.body) == json.loads(HG_NODE_HISTORY)
 
     def test_file_annotation_history_git(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='history',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url(controller='files', action='history',
+                                    repo_name=base.GIT_REPO,
                                     revision='master',
                                     f_path='vcs/nodes.py',
                                     annotate=True),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
 
-        assert response.body == GIT_NODE_HISTORY
+        assert json.loads(response.body) == json.loads(GIT_NODE_HISTORY)
 
     def test_file_authors(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='authors',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='authors',
+                                    repo_name=base.HG_REPO,
                                     revision='tip',
                                     f_path='vcs/nodes.py',
-                                    annotate=True))
+                                    annotate='1'))
         response.mustcontain('Marcin Kuzminski')
         response.mustcontain('Lukasz Balcerzak')
 
     def test_file_authors_git(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='authors',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url(controller='files', action='authors',
+                                    repo_name=base.GIT_REPO,
                                     revision='master',
                                     f_path='vcs/nodes.py',
-                                    annotate=True))
+                                    annotate='1'))
         response.mustcontain('Marcin Kuzminski')
         response.mustcontain('Lukasz Balcerzak')
 
     def test_archival(self):
         self.log_user()
-        _set_downloads(HG_REPO, set_to=True)
+        _set_downloads(base.HG_REPO, set_to=True)
         for arch_ext, info in ARCHIVE_SPECS.items():
             short = '27cd5cce30c9%s' % arch_ext
             fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
-            filename = '%s-%s' % (HG_REPO, short)
-            response = self.app.get(url(controller='files',
+            filename = '%s-%s' % (base.HG_REPO, short)
+            response = self.app.get(base.url(controller='files',
                                         action='archivefile',
-                                        repo_name=HG_REPO,
+                                        repo_name=base.HG_REPO,
                                         fname=fname))
 
             assert response.status == '200 OK'
@@ -210,29 +211,29 @@
                 ('Content-Disposition', 'attachment; filename=%s' % filename),
                 ('Content-Type', info[0]),
             ]
-            assert response.response._headers.items() == heads
+            assert sorted(response.response._headers.items()) == sorted(heads)
 
     def test_archival_wrong_ext(self):
         self.log_user()
-        _set_downloads(HG_REPO, set_to=True)
+        _set_downloads(base.HG_REPO, set_to=True)
         for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
             fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
 
-            response = self.app.get(url(controller='files',
+            response = self.app.get(base.url(controller='files',
                                         action='archivefile',
-                                        repo_name=HG_REPO,
+                                        repo_name=base.HG_REPO,
                                         fname=fname))
             response.mustcontain('Unknown archive type')
 
     def test_archival_wrong_revision(self):
         self.log_user()
-        _set_downloads(HG_REPO, set_to=True)
+        _set_downloads(base.HG_REPO, set_to=True)
         for rev in ['00x000000', 'tar', 'wrong', '@##$@$42413232', '232dffcd']:
             fname = '%s.zip' % rev
 
-            response = self.app.get(url(controller='files',
+            response = self.app.get(base.url(controller='files',
                                         action='archivefile',
-                                        repo_name=HG_REPO,
+                                        repo_name=base.HG_REPO,
                                         fname=fname))
             response.mustcontain('Unknown revision')
 
@@ -241,8 +242,8 @@
     #==========================================================================
     def test_raw_file_ok(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='rawfile',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='rawfile',
+                                    repo_name=base.HG_REPO,
                                     revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
                                     f_path='vcs/nodes.py'))
 
@@ -254,8 +255,8 @@
         rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
         f_path = 'vcs/nodes.py'
 
-        response = self.app.get(url(controller='files', action='rawfile',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='rawfile',
+                                    repo_name=base.HG_REPO,
                                     revision=rev,
                                     f_path=f_path), status=404)
 
@@ -266,12 +267,12 @@
         self.log_user()
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
         f_path = 'vcs/ERRORnodes.py'
-        response = self.app.get(url(controller='files', action='rawfile',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='rawfile',
+                                    repo_name=base.HG_REPO,
                                     revision=rev,
                                     f_path=f_path), status=404)
 
-        msg = "There is no file nor directory at the given path: &#39;%s&#39; at revision %s" % (f_path, rev[:12])
+        msg = "There is no file nor directory at the given path: &apos;%s&apos; at revision %s" % (f_path, rev[:12])
         response.mustcontain(msg)
 
     #==========================================================================
@@ -279,8 +280,8 @@
     #==========================================================================
     def test_raw_ok(self):
         self.log_user()
-        response = self.app.get(url(controller='files', action='raw',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='raw',
+                                    repo_name=base.HG_REPO,
                                     revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
                                     f_path='vcs/nodes.py'))
 
@@ -291,8 +292,8 @@
         rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
         f_path = 'vcs/nodes.py'
 
-        response = self.app.get(url(controller='files', action='raw',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='raw',
+                                    repo_name=base.HG_REPO,
                                     revision=rev,
                                     f_path=f_path), status=404)
 
@@ -303,18 +304,18 @@
         self.log_user()
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
         f_path = 'vcs/ERRORnodes.py'
-        response = self.app.get(url(controller='files', action='raw',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url(controller='files', action='raw',
+                                    repo_name=base.HG_REPO,
                                     revision=rev,
                                     f_path=f_path), status=404)
-        msg = "There is no file nor directory at the given path: &#39;%s&#39; at revision %s" % (f_path, rev[:12])
+        msg = "There is no file nor directory at the given path: &apos;%s&apos; at revision %s" % (f_path, rev[:12])
         response.mustcontain(msg)
 
     def test_ajaxed_files_list(self):
         self.log_user()
         rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
         response = self.app.get(
-            url('files_nodelist_home', repo_name=HG_REPO, f_path='/',
+            base.url('files_nodelist_home', repo_name=base.HG_REPO, f_path='/',
                 revision=rev),
             extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
         )
@@ -323,14 +324,14 @@
     # Hg - ADD FILE
     def test_add_file_view_hg(self):
         self.log_user()
-        response = self.app.get(url('files_add_home',
-                                      repo_name=HG_REPO,
+        response = self.app.get(base.url('files_add_home',
+                                      repo_name=base.HG_REPO,
                                       revision='tip', f_path='/'))
 
     def test_add_file_into_hg_missing_content(self):
         self.log_user()
-        response = self.app.post(url('files_add_home',
-                                      repo_name=HG_REPO,
+        response = self.app.post(base.url('files_add_home',
+                                      repo_name=base.HG_REPO,
                                       revision='tip', f_path='/'),
                                  params={
                                     'content': '',
@@ -342,8 +343,8 @@
 
     def test_add_file_into_hg_missing_filename(self):
         self.log_user()
-        response = self.app.post(url('files_add_home',
-                                      repo_name=HG_REPO,
+        response = self.app.post(base.url('files_add_home',
+                                      repo_name=base.HG_REPO,
                                       revision='tip', f_path='/'),
                                  params={
                                     'content': "foo",
@@ -353,15 +354,15 @@
 
         self.checkSessionFlash(response, 'No filename')
 
-    @parametrize('location,filename', [
+    @base.parametrize('location,filename', [
         ('/abs', 'foo'),
         ('../rel', 'foo'),
         ('file/../foo', 'foo'),
     ])
     def test_add_file_into_hg_bad_filenames(self, location, filename):
         self.log_user()
-        response = self.app.post(url('files_add_home',
-                                      repo_name=HG_REPO,
+        response = self.app.post(base.url('files_add_home',
+                                      repo_name=base.HG_REPO,
                                       revision='tip', f_path='/'),
                                  params={
                                     'content': "foo",
@@ -373,7 +374,7 @@
 
         self.checkSessionFlash(response, 'Location must be relative path and must not contain .. in path')
 
-    @parametrize('cnt,location,filename', [
+    @base.parametrize('cnt,location,filename', [
         (1, '', 'foo.txt'),
         (2, 'dir', 'foo.rst'),
         (3, 'rel/dir', 'foo.bar'),
@@ -381,7 +382,7 @@
     def test_add_file_into_hg(self, cnt, location, filename):
         self.log_user()
         repo = fixture.create_repo(u'commit-test-%s' % cnt, repo_type='hg')
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
                                  params={
@@ -400,14 +401,14 @@
     # Git - add file
     def test_add_file_view_git(self):
         self.log_user()
-        response = self.app.get(url('files_add_home',
-                                      repo_name=GIT_REPO,
+        response = self.app.get(base.url('files_add_home',
+                                      repo_name=base.GIT_REPO,
                                       revision='tip', f_path='/'))
 
     def test_add_file_into_git_missing_content(self):
         self.log_user()
-        response = self.app.post(url('files_add_home',
-                                      repo_name=GIT_REPO,
+        response = self.app.post(base.url('files_add_home',
+                                      repo_name=base.GIT_REPO,
                                       revision='tip', f_path='/'),
                                  params={
                                      'content': '',
@@ -418,8 +419,8 @@
 
     def test_add_file_into_git_missing_filename(self):
         self.log_user()
-        response = self.app.post(url('files_add_home',
-                                      repo_name=GIT_REPO,
+        response = self.app.post(base.url('files_add_home',
+                                      repo_name=base.GIT_REPO,
                                       revision='tip', f_path='/'),
                                  params={
                                     'content': "foo",
@@ -429,15 +430,15 @@
 
         self.checkSessionFlash(response, 'No filename')
 
-    @parametrize('location,filename', [
+    @base.parametrize('location,filename', [
         ('/abs', 'foo'),
         ('../rel', 'foo'),
         ('file/../foo', 'foo'),
     ])
     def test_add_file_into_git_bad_filenames(self, location, filename):
         self.log_user()
-        response = self.app.post(url('files_add_home',
-                                      repo_name=GIT_REPO,
+        response = self.app.post(base.url('files_add_home',
+                                      repo_name=base.GIT_REPO,
                                       revision='tip', f_path='/'),
                                  params={
                                     'content': "foo",
@@ -449,7 +450,7 @@
 
         self.checkSessionFlash(response, 'Location must be relative path and must not contain .. in path')
 
-    @parametrize('cnt,location,filename', [
+    @base.parametrize('cnt,location,filename', [
         (1, '', 'foo.txt'),
         (2, 'dir', 'foo.rst'),
         (3, 'rel/dir', 'foo.bar'),
@@ -457,7 +458,7 @@
     def test_add_file_into_git(self, cnt, location, filename):
         self.log_user()
         repo = fixture.create_repo(u'commit-test-%s' % cnt, repo_type='git')
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
                                  params={
@@ -476,9 +477,18 @@
     # Hg - EDIT
     def test_edit_file_view_hg(self):
         self.log_user()
-        response = self.app.get(url('files_edit_home',
-                                      repo_name=HG_REPO,
+        response = self.app.get(base.url('files_edit_home',
+                                      repo_name=base.HG_REPO,
                                       revision='tip', f_path='vcs/nodes.py'))
+        # Odd error when on tip ...
+        self.checkSessionFlash(response, "You can only edit files with revision being a valid branch")
+        assert b"Commit Message" not in response.body
+
+        # Specify branch head revision to avoid "valid branch" error and get coverage of edit form
+        response = self.app.get(base.url('files_edit_home',
+                                      repo_name=base.HG_REPO,
+                                      revision='96507bd11ecc815ebc6270fdf6db110928c09c1e', f_path='vcs/nodes.py'))
+        assert b"Commit Message" in response.body
 
     def test_edit_file_view_not_on_branch_hg(self):
         self.log_user()
@@ -487,7 +497,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
                                  params={
@@ -501,7 +511,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.get(url('files_edit_home',
+            response = self.app.get(base.url('files_edit_home',
                                           repo_name=repo.repo_name,
                                           revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
@@ -517,7 +527,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip',
                                       f_path='/'),
@@ -532,7 +542,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.post(url('files_edit_home',
+            response = self.app.post(base.url('files_edit_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
                                           f_path=posixpath.join(location, filename)),
@@ -550,8 +560,8 @@
     # Git - edit
     def test_edit_file_view_git(self):
         self.log_user()
-        response = self.app.get(url('files_edit_home',
-                                      repo_name=GIT_REPO,
+        response = self.app.get(base.url('files_edit_home',
+                                      repo_name=base.GIT_REPO,
                                       revision='tip', f_path='vcs/nodes.py'))
 
     def test_edit_file_view_not_on_branch_git(self):
@@ -561,7 +571,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
                                  params={
@@ -575,7 +585,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.get(url('files_edit_home',
+            response = self.app.get(base.url('files_edit_home',
                                           repo_name=repo.repo_name,
                                           revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
@@ -591,7 +601,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip',
                                       f_path='/'),
@@ -606,7 +616,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.post(url('files_edit_home',
+            response = self.app.post(base.url('files_edit_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
                                           f_path=posixpath.join(location, filename)),
@@ -624,8 +634,8 @@
     # Hg - delete
     def test_delete_file_view_hg(self):
         self.log_user()
-        response = self.app.get(url('files_delete_home',
-                                     repo_name=HG_REPO,
+        response = self.app.get(base.url('files_delete_home',
+                                     repo_name=base.HG_REPO,
                                      revision='tip', f_path='vcs/nodes.py'))
 
     def test_delete_file_view_not_on_branch_hg(self):
@@ -635,7 +645,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
                                  params={
@@ -649,7 +659,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.get(url('files_delete_home',
+            response = self.app.get(base.url('files_delete_home',
                                           repo_name=repo.repo_name,
                                           revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
@@ -665,7 +675,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip',
                                       f_path='/'),
@@ -680,7 +690,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.post(url('files_delete_home',
+            response = self.app.post(base.url('files_delete_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
                                           f_path=posixpath.join(location, filename)),
@@ -697,8 +707,8 @@
     # Git - delete
     def test_delete_file_view_git(self):
         self.log_user()
-        response = self.app.get(url('files_delete_home',
-                                     repo_name=HG_REPO,
+        response = self.app.get(base.url('files_delete_home',
+                                     repo_name=base.HG_REPO,
                                      revision='tip', f_path='vcs/nodes.py'))
 
     def test_delete_file_view_not_on_branch_git(self):
@@ -708,7 +718,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
                                  params={
@@ -722,7 +732,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.get(url('files_delete_home',
+            response = self.app.get(base.url('files_delete_home',
                                           repo_name=repo.repo_name,
                                           revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
@@ -738,7 +748,7 @@
         ## add file
         location = 'vcs'
         filename = 'nodes.py'
-        response = self.app.post(url('files_add_home',
+        response = self.app.post(base.url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip',
                                       f_path='/'),
@@ -753,7 +763,7 @@
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
                                    % posixpath.join(location, filename))
-            response = self.app.post(url('files_delete_home',
+            response = self.app.post(base.url('files_delete_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
                                           f_path=posixpath.join(location, filename)),
@@ -769,16 +779,16 @@
 
     def test_png_diff_no_crash_hg(self):
         self.log_user()
-        response = self.app.get(url('files_diff_home',
-                                    repo_name=HG_REPO,
+        response = self.app.get(base.url('files_diff_home',
+                                    repo_name=base.HG_REPO,
                                     f_path='docs/theme/ADC/static/documentation.png',
                                     diff1='tip', diff2='tip'))
         response.mustcontain("""<pre>Binary file</pre>""")
 
     def test_png_diff_no_crash_git(self):
         self.log_user()
-        response = self.app.get(url('files_diff_home',
-                                    repo_name=GIT_REPO,
+        response = self.app.get(base.url('files_diff_home',
+                                    repo_name=base.GIT_REPO,
                                     f_path='docs/theme/ADC/static/documentation.png',
                                     diff1='master', diff2='master'))
         response.mustcontain("""<pre>Binary file</pre>""")
--- a/kallithea/tests/functional/test_followers.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_followers.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,24 +1,24 @@
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestFollowersController(TestController):
+class TestFollowersController(base.TestController):
 
     def test_index_hg(self):
         self.log_user()
-        repo_name = HG_REPO
-        response = self.app.get(url(controller='followers',
+        repo_name = base.HG_REPO
+        response = self.app.get(base.url(controller='followers',
                                     action='followers',
                                     repo_name=repo_name))
 
-        response.mustcontain(TEST_USER_ADMIN_LOGIN)
+        response.mustcontain(base.TEST_USER_ADMIN_LOGIN)
         response.mustcontain("""Started following""")
 
     def test_index_git(self):
         self.log_user()
-        repo_name = GIT_REPO
-        response = self.app.get(url(controller='followers',
+        repo_name = base.GIT_REPO
+        response = self.app.get(base.url(controller='followers',
                                     action='followers',
                                     repo_name=repo_name))
 
-        response.mustcontain(TEST_USER_ADMIN_LOGIN)
+        response.mustcontain(base.TEST_USER_ADMIN_LOGIN)
         response.mustcontain("""Started following""")
--- a/kallithea/tests/functional/test_forks.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_forks.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,20 +1,19 @@
 # -*- coding: utf-8 -*-
 
-import urllib
+import urllib.parse
 
-from kallithea.lib.utils2 import safe_str, safe_unicode
 from kallithea.model.db import Repository, User
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class _BaseTestCase(TestController):
+class _BaseTestCase(base.TestController):
     """
     Write all tests here
     """
@@ -37,13 +36,13 @@
     def test_index(self):
         self.log_user()
         repo_name = self.REPO
-        response = self.app.get(url(controller='forks', action='forks',
+        response = self.app.get(base.url(controller='forks', action='forks',
                                     repo_name=repo_name))
 
         response.mustcontain("""There are no forks yet""")
 
     def test_no_permissions_to_fork(self):
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)['user_id']
+        self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)['user_id']
         try:
             user_model = UserModel()
             usr = User.get_default_user()
@@ -52,7 +51,7 @@
             Session().commit()
             # try create a fork
             repo_name = self.REPO
-            self.app.post(url(controller='forks', action='fork_create',
+            self.app.post(base.url(controller='forks', action='fork_create',
                               repo_name=repo_name), {'_session_csrf_secret_token': self.session_csrf_secret_token()}, status=403)
         finally:
             usr = User.get_default_user()
@@ -78,10 +77,10 @@
             'landing_rev': 'rev:tip',
             '_session_csrf_secret_token': self.session_csrf_secret_token()}
 
-        self.app.post(url(controller='forks', action='fork_create',
+        self.app.post(base.url(controller='forks', action='fork_create',
                           repo_name=repo_name), creation_args)
 
-        response = self.app.get(url(controller='forks', action='forks',
+        response = self.app.get(base.url(controller='forks', action='forks',
                                     repo_name=repo_name))
 
         response.mustcontain(
@@ -89,7 +88,7 @@
         )
 
         # remove this fork
-        response = self.app.post(url('delete_repo', repo_name=fork_name),
+        response = self.app.post(base.url('delete_repo', repo_name=fork_name),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
     def test_fork_create_into_group(self):
@@ -110,13 +109,13 @@
             'private': 'False',
             'landing_rev': 'rev:tip',
             '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        self.app.post(url(controller='forks', action='fork_create',
+        self.app.post(base.url(controller='forks', action='fork_create',
                           repo_name=repo_name), creation_args)
         repo = Repository.get_by_repo_name(fork_name_full)
         assert repo.fork.repo_name == self.REPO
 
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
+        response = self.app.get(base.url('repo_check_home', repo_name=fork_name_full))
         # test if we have a message that fork is ok
         self.checkSessionFlash(response,
                 'Forked repository %s as <a href="/%s">%s</a>'
@@ -130,7 +129,7 @@
         assert fork_repo.fork.repo_name == repo_name
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=fork_name_full))
+        response = self.app.get(base.url('summary_home', repo_name=fork_name_full))
         response.mustcontain(fork_name_full)
         response.mustcontain(self.REPO_TYPE)
         response.mustcontain('Fork of "<a href="/%s">%s</a>"' % (repo_name, repo_name))
@@ -144,7 +143,7 @@
         # create a fork
         repo_name = self.REPO
         org_repo = Repository.get_by_repo_name(repo_name)
-        fork_name = safe_str(self.REPO_FORK + u'-rødgrød')
+        fork_name = self.REPO_FORK + u'-rødgrød'
         creation_args = {
             'repo_name': fork_name,
             'repo_group': u'-1',
@@ -154,18 +153,18 @@
             'private': 'False',
             'landing_rev': 'rev:tip',
             '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        self.app.post(url(controller='forks', action='fork_create',
+        self.app.post(base.url(controller='forks', action='fork_create',
                           repo_name=repo_name), creation_args)
-        response = self.app.get(url(controller='forks', action='forks',
+        response = self.app.get(base.url(controller='forks', action='forks',
                                     repo_name=repo_name))
         response.mustcontain(
-            """<a href="/%s">%s</a>""" % (urllib.quote(fork_name), fork_name)
+            """<a href="/%s">%s</a>""" % (urllib.parse.quote(fork_name), fork_name)
         )
-        fork_repo = Repository.get_by_repo_name(safe_unicode(fork_name))
+        fork_repo = Repository.get_by_repo_name(fork_name)
         assert fork_repo
 
         # fork the fork
-        fork_name_2 = safe_str(self.REPO_FORK + u'-blåbærgrød')
+        fork_name_2 = self.REPO_FORK + u'-blåbærgrød'
         creation_args = {
             'repo_name': fork_name_2,
             'repo_group': u'-1',
@@ -175,18 +174,18 @@
             'private': 'False',
             'landing_rev': 'rev:tip',
             '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        self.app.post(url(controller='forks', action='fork_create',
+        self.app.post(base.url(controller='forks', action='fork_create',
                           repo_name=fork_name), creation_args)
-        response = self.app.get(url(controller='forks', action='forks',
+        response = self.app.get(base.url(controller='forks', action='forks',
                                     repo_name=fork_name))
         response.mustcontain(
-            """<a href="/%s">%s</a>""" % (urllib.quote(fork_name_2), fork_name_2)
+            """<a href="/%s">%s</a>""" % (urllib.parse.quote(fork_name_2), fork_name_2)
         )
 
         # remove these forks
-        response = self.app.post(url('delete_repo', repo_name=fork_name_2),
+        response = self.app.post(base.url('delete_repo', repo_name=fork_name_2),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
-        response = self.app.post(url('delete_repo', repo_name=fork_name),
+        response = self.app.post(base.url('delete_repo', repo_name=fork_name),
             params={'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
     def test_fork_create_and_permissions(self):
@@ -204,13 +203,13 @@
             'private': 'False',
             'landing_rev': 'rev:tip',
             '_session_csrf_secret_token': self.session_csrf_secret_token()}
-        self.app.post(url(controller='forks', action='fork_create',
+        self.app.post(base.url(controller='forks', action='fork_create',
                           repo_name=repo_name), creation_args)
         repo = Repository.get_by_repo_name(self.REPO_FORK)
         assert repo.fork.repo_name == self.REPO
 
         ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=fork_name))
+        response = self.app.get(base.url('repo_check_home', repo_name=fork_name))
         # test if we have a message that fork is ok
         self.checkSessionFlash(response,
                 'Forked repository %s as <a href="/%s">%s</a>'
@@ -224,7 +223,7 @@
         assert fork_repo.fork.repo_name == repo_name
 
         # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=fork_name))
+        response = self.app.get(base.url('summary_home', repo_name=fork_name))
         response.mustcontain(fork_name)
         response.mustcontain(self.REPO_TYPE)
         response.mustcontain('Fork of "<a href="/%s">%s</a>"' % (repo_name, repo_name))
@@ -242,7 +241,7 @@
                                           perm='repository.read')
         Session().commit()
 
-        response = self.app.get(url(controller='forks', action='forks',
+        response = self.app.get(base.url(controller='forks', action='forks',
                                     repo_name=repo_name))
 
         response.mustcontain('<div>fork of vcs test</div>')
@@ -257,7 +256,7 @@
             Session().commit()
 
             # fork shouldn't be visible
-            response = self.app.get(url(controller='forks', action='forks',
+            response = self.app.get(base.url(controller='forks', action='forks',
                                         repo_name=repo_name))
             response.mustcontain('There are no forks yet')
 
@@ -270,14 +269,14 @@
 
 
 class TestGIT(_BaseTestCase):
-    REPO = GIT_REPO
-    NEW_REPO = NEW_GIT_REPO
+    REPO = base.GIT_REPO
+    NEW_REPO = base.NEW_GIT_REPO
     REPO_TYPE = 'git'
-    REPO_FORK = GIT_FORK
+    REPO_FORK = base.GIT_FORK
 
 
 class TestHG(_BaseTestCase):
-    REPO = HG_REPO
-    NEW_REPO = NEW_HG_REPO
+    REPO = base.HG_REPO
+    NEW_REPO = base.NEW_HG_REPO
     REPO_TYPE = 'hg'
-    REPO_FORK = HG_FORK
+    REPO_FORK = base.HG_FORK
--- a/kallithea/tests/functional/test_home.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_home.py	Thu Feb 06 01:19:23 2020 +0100
@@ -4,18 +4,18 @@
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.repo_group import RepoGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestHomeController(TestController):
+class TestHomeController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='home', action='index'))
+        response = self.app.get(base.url(controller='home', action='index'))
         # if global permission is set
         response.mustcontain('Add Repository')
 
@@ -28,26 +28,26 @@
         )
 
         # html in javascript variable:
-        response.mustcontain(r'href=\"/%s\"' % HG_REPO)
+        response.mustcontain(r'href=\"/%s\"' % base.HG_REPO)
 
         response.mustcontain(r'\x3ci class=\"icon-globe\"')
 
         response.mustcontain(r'\"fixes issue with having custom format for git-log\n\"')
-        response.mustcontain(r'\"/%s/changeset/5f2c6ee195929b0be80749243c18121c9864a3b3\"' % GIT_REPO)
+        response.mustcontain(r'\"/%s/changeset/5f2c6ee195929b0be80749243c18121c9864a3b3\"' % base.GIT_REPO)
 
         response.mustcontain(r'\"disable security checks on hg clone for travis\"')
-        response.mustcontain(r'\"/%s/changeset/96507bd11ecc815ebc6270fdf6db110928c09c1e\"' % HG_REPO)
+        response.mustcontain(r'\"/%s/changeset/96507bd11ecc815ebc6270fdf6db110928c09c1e\"' % base.HG_REPO)
 
     def test_repo_summary_with_anonymous_access_disabled(self):
         with fixture.anon_access(False):
-            response = self.app.get(url(controller='summary',
-                                        action='index', repo_name=HG_REPO),
+            response = self.app.get(base.url(controller='summary',
+                                        action='index', repo_name=base.HG_REPO),
                                         status=302)
             assert 'login' in response.location
 
     def test_index_with_anonymous_access_disabled(self):
         with fixture.anon_access(False):
-            response = self.app.get(url(controller='home', action='index'),
+            response = self.app.get(base.url(controller='home', action='index'),
                                     status=302)
             assert 'login' in response.location
 
@@ -55,7 +55,7 @@
         self.log_user()
         gr = fixture.create_repo_group(u'gr1')
         fixture.create_repo(name=u'gr1/repo_in_group', repo_group=gr)
-        response = self.app.get(url('repos_group_home', group_name=u'gr1'))
+        response = self.app.get(base.url('repos_group_home', group_name=u'gr1'))
 
         try:
             response.mustcontain(u"gr1/repo_in_group")
@@ -67,21 +67,21 @@
     def test_users_and_groups_data(self):
         fixture.create_user('evil', firstname=u'D\'o\'ct"o"r', lastname=u'Évíl')
         fixture.create_user_group(u'grrrr', user_group_description=u"Groüp")
-        response = self.app.get(url('users_and_groups_data', query=u'evi'))
+        response = self.app.get(base.url('users_and_groups_data', query=u'evi'))
         assert response.status_code == 302
-        assert url('login_home') in response.location
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-        response = self.app.get(url('users_and_groups_data', query=u'evi'))
+        assert base.url('login_home') in response.location
+        self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)
+        response = self.app.get(base.url('users_and_groups_data', query=u'evi'))
         result = json.loads(response.body)['results']
         assert result[0].get('fname') == u'D\'o\'ct"o"r'
         assert result[0].get('lname') == u'Évíl'
-        response = self.app.get(url('users_and_groups_data', key=u'evil'))
+        response = self.app.get(base.url('users_and_groups_data', key=u'evil'))
         result = json.loads(response.body)['results']
         assert result[0].get('fname') == u'D\'o\'ct"o"r'
         assert result[0].get('lname') == u'Évíl'
-        response = self.app.get(url('users_and_groups_data', query=u'rrrr'))
+        response = self.app.get(base.url('users_and_groups_data', query=u'rrrr'))
         result = json.loads(response.body)['results']
         assert not result
-        response = self.app.get(url('users_and_groups_data', types='users,groups', query=u'rrrr'))
+        response = self.app.get(base.url('users_and_groups_data', types='users,groups', query=u'rrrr'))
         result = json.loads(response.body)['results']
         assert result[0].get('grname') == u'grrrr'
--- a/kallithea/tests/functional/test_journal.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_journal.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,13 +1,13 @@
 import datetime
 
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestJournalController(TestController):
+class TestJournalController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='journal', action='index'))
+        response = self.app.get(base.url(controller='journal', action='index'))
 
         response.mustcontain("""<h4>%s</h4>""" % datetime.date.today())
 
@@ -22,18 +22,18 @@
 #
 #        assert len(followings) == 1, 'Not following any repository'
 #
-#        response = self.app.post(url(controller='journal',
+#        response = self.app.post(base.url(controller='journal',
 #                                     action='toggle_following'),
 #                                     {'follows_repository_id':repo.repo_id})
 
     def test_start_following_repository(self):
         self.log_user()
-        response = self.app.get(url(controller='journal', action='index'),)
+        response = self.app.get(base.url(controller='journal', action='index'),)
 
     def test_public_journal_atom(self):
         self.log_user()
-        response = self.app.get(url(controller='journal', action='public_journal_atom'),)
+        response = self.app.get(base.url(controller='journal', action='public_journal_atom'),)
 
     def test_public_journal_rss(self):
         self.log_user()
-        response = self.app.get(url(controller='journal', action='public_journal_rss'),)
+        response = self.app.get(base.url(controller='journal', action='public_journal_rss'),)
--- a/kallithea/tests/functional/test_login.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_login.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,10 +1,12 @@
 # -*- coding: utf-8 -*-
 import re
 import time
-import urlparse
+import urllib.parse
 
+import mock
 from tg.util.webtest import test_context
 
+import kallithea.lib.celerylib.tasks
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import check_password
 from kallithea.lib.utils2 import generate_api_key
@@ -13,61 +15,61 @@
 from kallithea.model.db import User
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestLoginController(TestController):
+class TestLoginController(base.TestController):
 
     def test_index(self):
-        response = self.app.get(url(controller='login', action='index'))
+        response = self.app.get(base.url(controller='login', action='index'))
         assert response.status == '200 OK'
         # Test response...
 
     def test_login_admin_ok(self):
-        response = self.app.post(url(controller='login', action='index'),
-                                 {'username': TEST_USER_ADMIN_LOGIN,
-                                  'password': TEST_USER_ADMIN_PASS,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_ADMIN_LOGIN,
+                                  'password': base.TEST_USER_ADMIN_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
         assert response.status == '302 Found'
-        self.assert_authenticated_user(response, TEST_USER_ADMIN_LOGIN)
+        self.assert_authenticated_user(response, base.TEST_USER_ADMIN_LOGIN)
 
         response = response.follow()
-        response.mustcontain('/%s' % HG_REPO)
+        response.mustcontain('/%s' % base.HG_REPO)
 
     def test_login_regular_ok(self):
-        response = self.app.post(url(controller='login', action='index'),
-                                 {'username': TEST_USER_REGULAR_LOGIN,
-                                  'password': TEST_USER_REGULAR_PASS,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_REGULAR_LOGIN,
+                                  'password': base.TEST_USER_REGULAR_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         assert response.status == '302 Found'
-        self.assert_authenticated_user(response, TEST_USER_REGULAR_LOGIN)
+        self.assert_authenticated_user(response, base.TEST_USER_REGULAR_LOGIN)
 
         response = response.follow()
-        response.mustcontain('/%s' % HG_REPO)
+        response.mustcontain('/%s' % base.HG_REPO)
 
     def test_login_regular_email_ok(self):
-        response = self.app.post(url(controller='login', action='index'),
-                                 {'username': TEST_USER_REGULAR_EMAIL,
-                                  'password': TEST_USER_REGULAR_PASS,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_REGULAR_EMAIL,
+                                  'password': base.TEST_USER_REGULAR_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         assert response.status == '302 Found'
-        self.assert_authenticated_user(response, TEST_USER_REGULAR_LOGIN)
+        self.assert_authenticated_user(response, base.TEST_USER_REGULAR_LOGIN)
 
         response = response.follow()
-        response.mustcontain('/%s' % HG_REPO)
+        response.mustcontain('/%s' % base.HG_REPO)
 
     def test_login_ok_came_from(self):
         test_came_from = '/_admin/users'
-        response = self.app.post(url(controller='login', action='index',
+        response = self.app.post(base.url(controller='login', action='index',
                                      came_from=test_came_from),
-                                 {'username': TEST_USER_ADMIN_LOGIN,
-                                  'password': TEST_USER_ADMIN_PASS,
+                                 {'username': base.TEST_USER_ADMIN_LOGIN,
+                                  'password': base.TEST_USER_ADMIN_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
         assert response.status == '302 Found'
         response = response.follow()
@@ -76,9 +78,9 @@
         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,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_REGULAR_LOGIN,
+                                  'password': base.TEST_USER_REGULAR_PASS,
                                   'remember': False,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
@@ -87,9 +89,9 @@
             assert not 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,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_REGULAR_LOGIN,
+                                  'password': base.TEST_USER_REGULAR_PASS,
                                   'remember': True,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
@@ -98,23 +100,23 @@
             assert 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,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_REGULAR_LOGIN,
+                                  'password': base.TEST_USER_REGULAR_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         # Verify that a login session has been established.
-        response = self.app.get(url(controller='login', action='index'))
+        response = self.app.get(base.url(controller='login', action='index'))
         response = response.follow()
         assert 'authuser' in response.session
 
         response.click('Log Out')
 
         # Verify that the login session has been terminated.
-        response = self.app.get(url(controller='login', action='index'))
+        response = self.app.get(base.url(controller='login', action='index'))
         assert 'authuser' not in response.session
 
-    @parametrize('url_came_from', [
+    @base.parametrize('url_came_from', [
           ('data:text/html,<script>window.alert("xss")</script>',),
           ('mailto:test@example.com',),
           ('file:///etc/passwd',),
@@ -126,16 +128,16 @@
           ('non-absolute-path',),
     ])
     def test_login_bad_came_froms(self, url_came_from):
-        response = self.app.post(url(controller='login', action='index',
+        response = self.app.post(base.url(controller='login', action='index',
                                      came_from=url_came_from),
-                                 {'username': TEST_USER_ADMIN_LOGIN,
-                                  'password': TEST_USER_ADMIN_PASS,
+                                 {'username': base.TEST_USER_ADMIN_LOGIN,
+                                  'password': base.TEST_USER_ADMIN_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()},
                                  status=400)
 
     def test_login_short_password(self):
-        response = self.app.post(url(controller='login', action='index'),
-                                 {'username': TEST_USER_ADMIN_LOGIN,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_ADMIN_LOGIN,
                                   'password': 'as',
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
         assert response.status == '200 OK'
@@ -143,7 +145,7 @@
         response.mustcontain('Enter 3 characters or more')
 
     def test_login_wrong_username_password(self):
-        response = self.app.post(url(controller='login', action='index'),
+        response = self.app.post(base.url(controller='login', action='index'),
                                  {'username': 'error',
                                   'password': 'test12',
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -151,8 +153,8 @@
         response.mustcontain('Invalid username or password')
 
     def test_login_non_ascii(self):
-        response = self.app.post(url(controller='login', action='index'),
-                                 {'username': TEST_USER_REGULAR_LOGIN,
+        response = self.app.post(base.url(controller='login', action='index'),
+                                 {'username': base.TEST_USER_REGULAR_LOGIN,
                                   'password': 'blåbærgrød',
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
@@ -160,63 +162,61 @@
 
     # verify that get arguments are correctly passed along login redirection
 
-    @parametrize('args,args_encoded', [
-        ({'foo':'one', 'bar':'two'}, (('foo', 'one'), ('bar', 'two'))),
-        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
-             (('blue', u'blå'.encode('utf-8')), ('green', u'grøn'.encode('utf-8')))),
+    @base.parametrize('args', [
+        {'foo':'one', 'bar':'two'},
+        {'blue': u'blå', 'green': u'grøn'},
     ])
-    def test_redirection_to_login_form_preserves_get_args(self, args, args_encoded):
+    def test_redirection_to_login_form_preserves_get_args(self, args):
         with fixture.anon_access(False):
-            response = self.app.get(url(controller='summary', action='index',
-                                        repo_name=HG_REPO,
+            response = self.app.get(base.url(controller='summary', action='index',
+                                        repo_name=base.HG_REPO,
                                         **args))
             assert response.status == '302 Found'
-            came_from = urlparse.parse_qs(urlparse.urlparse(response.location).query)['came_from'][0]
-            came_from_qs = urlparse.parse_qsl(urlparse.urlparse(came_from).query)
-            for encoded in args_encoded:
-                assert encoded in came_from_qs
+            came_from = urllib.parse.parse_qs(urllib.parse.urlparse(response.location).query)['came_from'][0]
+            came_from_qs = urllib.parse.parse_qsl(urllib.parse.urlparse(came_from).query)
+            assert sorted(came_from_qs) == sorted(args.items())
 
-    @parametrize('args,args_encoded', [
+    @base.parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
         ({'blue': u'blå', '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=url('/_admin/users', **args)))
-        came_from = urlparse.parse_qs(urlparse.urlparse(response.form.action).query)['came_from'][0]
+        response = self.app.get(base.url(controller='login', action='index',
+                                    came_from=base.url('/_admin/users', **args)))
+        came_from = urllib.parse.parse_qs(urllib.parse.urlparse(response.form.action).query)['came_from'][0]
         for encoded in args_encoded:
             assert encoded in came_from
 
-    @parametrize('args,args_encoded', [
+    @base.parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
         ({'blue': u'blå', '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=url('/_admin/users', **args)),
-                                 {'username': TEST_USER_ADMIN_LOGIN,
-                                  'password': TEST_USER_ADMIN_PASS,
+        response = self.app.post(base.url(controller='login', action='index',
+                                     came_from=base.url('/_admin/users', **args)),
+                                 {'username': base.TEST_USER_ADMIN_LOGIN,
+                                  'password': base.TEST_USER_ADMIN_PASS,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
         assert response.status == '302 Found'
         for encoded in args_encoded:
             assert encoded in response.location
 
-    @parametrize('args,args_encoded', [
+    @base.parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
         ({'blue': u'blå', '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=url('/_admin/users', **args)),
+        response = self.app.post(base.url(controller='login', action='index',
+                                     came_from=base.url('/_admin/users', **args)),
                                  {'username': 'error',
                                   'password': 'test12',
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         response.mustcontain('Invalid username or password')
-        came_from = urlparse.parse_qs(urlparse.urlparse(response.form.action).query)['came_from'][0]
+        came_from = urllib.parse.parse_qs(urllib.parse.urlparse(response.form.action).query)['came_from'][0]
         for encoded in args_encoded:
             assert encoded in came_from
 
@@ -224,12 +224,12 @@
     # REGISTRATIONS
     #==========================================================================
     def test_register(self):
-        response = self.app.get(url(controller='login', action='register'))
+        response = self.app.get(base.url(controller='login', action='register'))
         response.mustcontain('Sign Up')
 
     def test_register_err_same_username(self):
-        uname = TEST_USER_ADMIN_LOGIN
-        response = self.app.post(url(controller='login', action='register'),
+        uname = base.TEST_USER_ADMIN_LOGIN
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': uname,
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
@@ -244,11 +244,11 @@
         response.mustcontain(msg)
 
     def test_register_err_same_email(self):
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': 'test_admin_0',
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
-                                             'email': TEST_USER_ADMIN_EMAIL,
+                                             'email': base.TEST_USER_ADMIN_EMAIL,
                                              'firstname': 'test',
                                              'lastname': 'test',
                                              '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -258,11 +258,11 @@
         response.mustcontain(msg)
 
     def test_register_err_same_email_case_sensitive(self):
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': 'test_admin_1',
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
-                                             'email': TEST_USER_ADMIN_EMAIL.title(),
+                                             'email': base.TEST_USER_ADMIN_EMAIL.title(),
                                              'firstname': 'test',
                                              'lastname': 'test',
                                              '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -271,7 +271,7 @@
         response.mustcontain(msg)
 
     def test_register_err_wrong_data(self):
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': 'xs',
                                              'password': 'test',
                                              'password_confirmation': 'test',
@@ -284,7 +284,7 @@
         response.mustcontain('Enter a value 6 characters long or more')
 
     def test_register_err_username(self):
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': 'error user',
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
@@ -300,8 +300,8 @@
                 'alphanumeric character')
 
     def test_register_err_case_sensitive(self):
-        usr = TEST_USER_ADMIN_LOGIN.title()
-        response = self.app.post(url(controller='login', action='register'),
+        usr = base.TEST_USER_ADMIN_LOGIN.title()
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': usr,
                                              'password': 'test12',
                                              'password_confirmation': 'test12',
@@ -317,7 +317,7 @@
         response.mustcontain(msg)
 
     def test_register_special_chars(self):
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                         {'username': 'xxxaxn',
                                          'password': 'ąćźżąśśśś',
                                          'password_confirmation': 'ąćźżąśśśś',
@@ -331,7 +331,7 @@
         response.mustcontain(msg)
 
     def test_register_password_mismatch(self):
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': 'xs',
                                              'password': '123qwe',
                                              'password_confirmation': 'qwe123',
@@ -350,7 +350,7 @@
         name = 'testname'
         lastname = 'testlastname'
 
-        response = self.app.post(url(controller='login', action='register'),
+        response = self.app.post(base.url(controller='login', action='register'),
                                             {'username': username,
                                              'password': password,
                                              'password_confirmation': password,
@@ -378,14 +378,14 @@
     def test_forgot_password_wrong_mail(self):
         bad_email = 'username%wrongmail.org'
         response = self.app.post(
-                        url(controller='login', action='password_reset'),
+                        base.url(controller='login', action='password_reset'),
                             {'email': bad_email,
                              '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         response.mustcontain('An email address must contain a single @')
 
     def test_forgot_password(self):
-        response = self.app.get(url(controller='login',
+        response = self.app.get(base.url(controller='login',
                                     action='password_reset'))
         assert response.status == '200 OK'
 
@@ -406,26 +406,47 @@
         Session().add(new)
         Session().commit()
 
-        response = self.app.post(url(controller='login',
-                                     action='password_reset'),
-                                 {'email': email,
-                                  '_session_csrf_secret_token': self.session_csrf_secret_token()})
+        token = UserModel().get_reset_password_token(
+            User.get_by_username(username), timestamp, self.session_csrf_secret_token())
+
+        collected = []
+        def mock_send_email(recipients, subject, body='', html_body='', headers=None, author=None):
+            collected.append((recipients, subject, body, html_body))
+
+        with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', mock_send_email), \
+                mock.patch.object(time, 'time', lambda: timestamp):
+            response = self.app.post(base.url(controller='login',
+                                         action='password_reset'),
+                                     {'email': email,
+                                      '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         self.checkSessionFlash(response, 'A password reset confirmation code has been sent')
 
+        ((recipients, subject, body, html_body),) = collected
+        assert recipients == ['username@example.com']
+        assert subject == 'Password reset link'
+        assert '\n%s\n' % token in body
+        (confirmation_url,) = (line for line in body.splitlines() if line.startswith('http://'))
+        assert ' href="%s"' % confirmation_url.replace('&', '&amp;').replace('@', '%40') in html_body
+
+        d = urllib.parse.parse_qs(urllib.parse.urlparse(confirmation_url).query)
+        assert d['token'] == [token]
+        assert d['timestamp'] == [str(timestamp)]
+        assert d['email'] == [email]
+
         response = response.follow()
 
         # BAD TOKEN
 
-        token = "bad"
+        bad_token = "bad"
 
-        response = self.app.post(url(controller='login',
+        response = self.app.post(base.url(controller='login',
                                      action='password_reset_confirmation'),
                                  {'email': email,
                                   'timestamp': timestamp,
                                   'password': "p@ssw0rd",
                                   'password_confirm': "p@ssw0rd",
-                                  'token': token,
+                                  'token': bad_token,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                  })
         assert response.status == '200 OK'
@@ -433,21 +454,17 @@
 
         # GOOD TOKEN
 
-        # TODO: The token should ideally be taken from the mail sent
-        # above, instead of being recalculated.
-
-        token = UserModel().get_reset_password_token(
-            User.get_by_username(username), timestamp, self.session_csrf_secret_token())
-
-        response = self.app.get(url(controller='login',
-                                    action='password_reset_confirmation',
-                                    email=email,
-                                    timestamp=timestamp,
-                                    token=token))
+        response = self.app.get(confirmation_url)
         assert response.status == '200 OK'
         response.mustcontain("You are about to set a new password for the email address %s" % email)
+        response.mustcontain('<form action="%s" method="post">' % base.url(controller='login', action='password_reset_confirmation'))
+        response.mustcontain('value="%s"' % self.session_csrf_secret_token())
+        response.mustcontain('value="%s"' % token)
+        response.mustcontain('value="%s"' % timestamp)
+        response.mustcontain('value="username@example.com"')
 
-        response = self.app.post(url(controller='login',
+        # fake a submit of that form
+        response = self.app.post(base.url(controller='login',
                                      action='password_reset_confirmation'),
                                  {'email': email,
                                   'timestamp': timestamp,
@@ -483,16 +500,16 @@
                 params = {'api_key': api_key}
                 headers = {'Authorization': 'Bearer ' + str(api_key)}
 
-            self.app.get(url(controller='changeset', action='changeset_raw',
-                             repo_name=HG_REPO, revision='tip', **params),
+            self.app.get(base.url(controller='changeset', action='changeset_raw',
+                             repo_name=base.HG_REPO, revision='tip', **params),
                          status=status)
 
-            self.app.get(url(controller='changeset', action='changeset_raw',
-                             repo_name=HG_REPO, revision='tip'),
+            self.app.get(base.url(controller='changeset', action='changeset_raw',
+                             repo_name=base.HG_REPO, revision='tip'),
                          headers=headers,
                          status=status)
 
-    @parametrize('test_name,api_key,code', [
+    @base.parametrize('test_name,api_key,code', [
         ('none', None, 302),
         ('empty_string', '', 403),
         ('fake_number', '123456', 403),
@@ -504,12 +521,12 @@
         self._api_key_test(api_key, code)
 
     def test_access_page_via_extra_api_key(self):
-        new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
+        new_api_key = ApiKeyModel().create(base.TEST_USER_ADMIN_LOGIN, u'test')
         Session().commit()
         self._api_key_test(new_api_key.api_key, status=200)
 
     def test_access_page_via_expired_api_key(self):
-        new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
+        new_api_key = ApiKeyModel().create(base.TEST_USER_ADMIN_LOGIN, u'test')
         Session().commit()
         # patch the API key and make it expired
         new_api_key.expires = 0
--- a/kallithea/tests/functional/test_my_account.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_my_account.py	Thu Feb 06 01:19:23 2020 +0100
@@ -6,14 +6,14 @@
 from kallithea.model.db import Repository, User, UserApiKeys, UserFollowing, UserSshKeys
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestMyAccountController(TestController):
+class TestMyAccountController(base.TestController):
     test_user_1 = 'testme'
 
     @classmethod
@@ -24,74 +24,74 @@
 
     def test_my_account(self):
         self.log_user()
-        response = self.app.get(url('my_account'))
+        response = self.app.get(base.url('my_account'))
 
-        response.mustcontain('value="%s' % TEST_USER_ADMIN_LOGIN)
+        response.mustcontain('value="%s' % base.TEST_USER_ADMIN_LOGIN)
 
     def test_my_account_my_repos(self):
         self.log_user()
-        response = self.app.get(url('my_account_repos'))
+        response = self.app.get(base.url('my_account_repos'))
         cnt = Repository.query().filter(Repository.owner ==
-                           User.get_by_username(TEST_USER_ADMIN_LOGIN)).count()
-        response.mustcontain('"raw_name": "%s"' % HG_REPO)
-        response.mustcontain('"just_name": "%s"' % GIT_REPO)
+                           User.get_by_username(base.TEST_USER_ADMIN_LOGIN)).count()
+        response.mustcontain('"raw_name": "%s"' % base.HG_REPO)
+        response.mustcontain('"just_name": "%s"' % base.GIT_REPO)
 
     def test_my_account_my_watched(self):
         self.log_user()
-        response = self.app.get(url('my_account_watched'))
+        response = self.app.get(base.url('my_account_watched'))
 
         cnt = UserFollowing.query().filter(UserFollowing.user ==
-                            User.get_by_username(TEST_USER_ADMIN_LOGIN)).count()
-        response.mustcontain('"raw_name": "%s"' % HG_REPO)
-        response.mustcontain('"just_name": "%s"' % GIT_REPO)
+                            User.get_by_username(base.TEST_USER_ADMIN_LOGIN)).count()
+        response.mustcontain('"raw_name": "%s"' % base.HG_REPO)
+        response.mustcontain('"just_name": "%s"' % base.GIT_REPO)
 
     def test_my_account_my_emails(self):
         self.log_user()
-        response = self.app.get(url('my_account_emails'))
+        response = self.app.get(base.url('my_account_emails'))
         response.mustcontain('No additional emails specified')
 
     def test_my_account_my_emails_add_existing_email(self):
         self.log_user()
-        response = self.app.get(url('my_account_emails'))
+        response = self.app.get(base.url('my_account_emails'))
         response.mustcontain('No additional emails specified')
-        response = self.app.post(url('my_account_emails'),
-                                 {'new_email': TEST_USER_REGULAR_EMAIL, '_session_csrf_secret_token': self.session_csrf_secret_token()})
+        response = self.app.post(base.url('my_account_emails'),
+                                 {'new_email': base.TEST_USER_REGULAR_EMAIL, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'This email address is already in use')
 
     def test_my_account_my_emails_add_missing_email_in_form(self):
         self.log_user()
-        response = self.app.get(url('my_account_emails'))
+        response = self.app.get(base.url('my_account_emails'))
         response.mustcontain('No additional emails specified')
-        response = self.app.post(url('my_account_emails'),
+        response = self.app.post(base.url('my_account_emails'),
             {'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Please enter an email address')
 
     def test_my_account_my_emails_add_remove(self):
         self.log_user()
-        response = self.app.get(url('my_account_emails'))
+        response = self.app.get(base.url('my_account_emails'))
         response.mustcontain('No additional emails specified')
 
-        response = self.app.post(url('my_account_emails'),
+        response = self.app.post(base.url('my_account_emails'),
                                  {'new_email': 'barz@example.com', '_session_csrf_secret_token': self.session_csrf_secret_token()})
 
-        response = self.app.get(url('my_account_emails'))
+        response = self.app.get(base.url('my_account_emails'))
 
         from kallithea.model.db import UserEmailMap
         email_id = UserEmailMap.query() \
-            .filter(UserEmailMap.user == User.get_by_username(TEST_USER_ADMIN_LOGIN)) \
+            .filter(UserEmailMap.user == User.get_by_username(base.TEST_USER_ADMIN_LOGIN)) \
             .filter(UserEmailMap.email == 'barz@example.com').one().email_id
 
         response.mustcontain('barz@example.com')
         response.mustcontain('<input id="del_email_id" name="del_email_id" type="hidden" value="%s" />' % email_id)
 
-        response = self.app.post(url('my_account_emails_delete'),
+        response = self.app.post(base.url('my_account_emails_delete'),
                                  {'del_email_id': email_id, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Removed email from user')
-        response = self.app.get(url('my_account_emails'))
+        response = self.app.get(base.url('my_account_emails'))
         response.mustcontain('No additional emails specified')
 
 
-    @parametrize('name,attrs',
+    @base.parametrize('name,attrs',
         [('firstname', {'firstname': 'new_username'}),
          ('lastname', {'lastname': 'new_username'}),
          ('admin', {'admin': True}),
@@ -123,7 +123,7 @@
         params.update({'_session_csrf_secret_token': self.session_csrf_secret_token()})
 
         params.update(attrs)
-        response = self.app.post(url('my_account'), params)
+        response = self.app.post(base.url('my_account'), params)
 
         self.checkSessionFlash(response,
                                'Your account was updated successfully')
@@ -155,11 +155,11 @@
     def test_my_account_update_err_email_exists(self):
         self.log_user()
 
-        new_email = TEST_USER_REGULAR_EMAIL  # already existing email
-        response = self.app.post(url('my_account'),
+        new_email = base.TEST_USER_REGULAR_EMAIL  # already existing email
+        response = self.app.post(base.url('my_account'),
                                 params=dict(
-                                    username=TEST_USER_ADMIN_LOGIN,
-                                    new_password=TEST_USER_ADMIN_PASS,
+                                    username=base.TEST_USER_ADMIN_LOGIN,
+                                    new_password=base.TEST_USER_ADMIN_PASS,
                                     password_confirmation='test122',
                                     firstname=u'NewName',
                                     lastname=u'NewLastname',
@@ -170,13 +170,13 @@
         response.mustcontain('This email address is already in use')
 
     def test_my_account_update_err(self):
-        self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
+        self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.TEST_USER_REGULAR2_PASS)
 
         new_email = 'newmail.pl'
-        response = self.app.post(url('my_account'),
+        response = self.app.post(base.url('my_account'),
                                  params=dict(
-                                            username=TEST_USER_ADMIN_LOGIN,
-                                            new_password=TEST_USER_ADMIN_PASS,
+                                            username=base.TEST_USER_ADMIN_LOGIN,
+                                            new_password=base.TEST_USER_ADMIN_PASS,
                                             password_confirmation='test122',
                                             firstname=u'NewName',
                                             lastname=u'NewLastname',
@@ -188,25 +188,25 @@
         with test_context(self.app):
             msg = validators.ValidUsername(edit=False, old_data={}) \
                     ._messages['username_exists']
-        msg = h.html_escape(msg % {'username': TEST_USER_ADMIN_LOGIN})
+        msg = h.html_escape(msg % {'username': base.TEST_USER_ADMIN_LOGIN})
         response.mustcontain(msg)
 
     def test_my_account_api_keys(self):
-        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
+        usr = self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
-        response = self.app.get(url('my_account_api_keys'))
+        response = self.app.get(base.url('my_account_api_keys'))
         response.mustcontain(user.api_key)
         response.mustcontain('Expires: Never')
 
-    @parametrize('desc,lifetime', [
+    @base.parametrize('desc,lifetime', [
         ('forever', -1),
         ('5mins', 60*5),
         ('30days', 60*60*24*30),
     ])
     def test_my_account_add_api_keys(self, desc, lifetime):
-        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
+        usr = self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
-        response = self.app.post(url('my_account_api_keys'),
+        response = self.app.post(base.url('my_account_api_keys'),
                                  {'description': desc, 'lifetime': lifetime, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully created')
         try:
@@ -220,9 +220,9 @@
                 Session().commit()
 
     def test_my_account_remove_api_key(self):
-        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
+        usr = self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.TEST_USER_REGULAR2_PASS)
         user = User.get(usr['user_id'])
-        response = self.app.post(url('my_account_api_keys'),
+        response = self.app.post(base.url('my_account_api_keys'),
                                  {'description': 'desc', 'lifetime': -1, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully created')
         response = response.follow()
@@ -231,21 +231,21 @@
         keys = UserApiKeys.query().all()
         assert 1 == len(keys)
 
-        response = self.app.post(url('my_account_api_keys_delete'),
+        response = self.app.post(base.url('my_account_api_keys_delete'),
                  {'del_api_key': keys[0].api_key, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully deleted')
         keys = UserApiKeys.query().all()
         assert 0 == len(keys)
 
     def test_my_account_reset_main_api_key(self):
-        usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
+        usr = self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.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 = self.app.get(base.url('my_account_api_keys'))
         response.mustcontain(api_key)
         response.mustcontain('Expires: Never')
 
-        response = self.app.post(url('my_account_api_keys_delete'),
+        response = self.app.post(base.url('my_account_api_keys_delete'),
                  {'del_api_key_builtin': api_key, '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'API key successfully reset')
         response = response.follow()
@@ -256,8 +256,8 @@
         public_key = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== me@localhost'
         fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8'
 
-        self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
-        response = self.app.post(url('my_account_ssh_keys'),
+        self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.TEST_USER_REGULAR2_PASS)
+        response = self.app.post(base.url('my_account_ssh_keys'),
                                  {'description': description,
                                   'public_key': public_key,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -277,8 +277,8 @@
         public_key = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== me@localhost'
         fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8'
 
-        self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
-        response = self.app.post(url('my_account_ssh_keys'),
+        self.log_user(base.TEST_USER_REGULAR2_LOGIN, base.TEST_USER_REGULAR2_PASS)
+        response = self.app.post(base.url('my_account_ssh_keys'),
                                  {'description': description,
                                   'public_key': public_key,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -288,8 +288,8 @@
         ssh_key = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).one()
         assert ssh_key.description == u'me@localhost'
 
-        response = self.app.post(url('my_account_ssh_keys_delete'),
-                                 {'del_public_key': ssh_key.public_key,
+        response = self.app.post(base.url('my_account_ssh_keys_delete'),
+                                 {'del_public_key_fingerprint': ssh_key.fingerprint,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'SSH key successfully deleted')
         keys = UserSshKeys.query().all()
--- a/kallithea/tests/functional/test_pullrequests.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_pullrequests.py	Thu Feb 06 01:19:23 2020 +0100
@@ -5,27 +5,27 @@
 from kallithea.controllers.pullrequests import PullrequestsController
 from kallithea.model.db import PullRequest, User
 from kallithea.model.meta import Session
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestPullrequestsController(TestController):
+class TestPullrequestsController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='pullrequests', action='index',
-                                    repo_name=HG_REPO))
+        response = self.app.get(base.url(controller='pullrequests', action='index',
+                                    repo_name=base.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,
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
+                                 {'org_repo': base.HG_REPO,
                                   'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
@@ -40,11 +40,11 @@
 
     def test_available(self):
         self.log_user()
-        response = self.app.post(url(controller='pullrequests', action='create',
-                                     repo_name=HG_REPO),
-                                 {'org_repo': HG_REPO,
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
+                                 {'org_repo': base.HG_REPO,
                                   'org_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
@@ -60,11 +60,11 @@
 
     def test_range(self):
         self.log_user()
-        response = self.app.post(url(controller='pullrequests', action='create',
-                                     repo_name=HG_REPO),
-                                 {'org_repo': HG_REPO,
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
+                                 {'org_repo': base.HG_REPO,
                                   'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
@@ -78,57 +78,57 @@
 
     def test_update_reviewers(self):
         self.log_user()
-        regular_user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
-        regular_user2 = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
-        admin_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        regular_user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
+        regular_user2 = User.get_by_username(base.TEST_USER_REGULAR2_LOGIN)
+        admin_user = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
 
         # create initial PR
-        response = self.app.post(url(controller='pullrequests', action='create',
-                                     repo_name=HG_REPO),
-                                 {'org_repo': HG_REPO,
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
+                                 {'org_repo': base.HG_REPO,
                                   'org_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                  },
                                  status=302)
-        pull_request1_id = re.search('/pull-request/(\d+)/', response.location).group(1)
-        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (HG_REPO, pull_request1_id)
+        pull_request1_id = re.search(r'/pull-request/(\d+)/', response.location).group(1)
+        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (base.HG_REPO, pull_request1_id)
 
         # create new iteration
-        response = self.app.post(url(controller='pullrequests', action='post',
-                                     repo_name=HG_REPO, pull_request_id=pull_request1_id),
+        response = self.app.post(base.url(controller='pullrequests', action='post',
+                                     repo_name=base.HG_REPO, pull_request_id=pull_request1_id),
                                  {
                                   'updaterev': '4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
-                                  'owner': TEST_USER_ADMIN_LOGIN,
+                                  'owner': base.TEST_USER_ADMIN_LOGIN,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                   'review_members': [regular_user.user_id],
                                  },
                                  status=302)
-        pull_request2_id = re.search('/pull-request/(\d+)/', response.location).group(1)
+        pull_request2_id = re.search(r'/pull-request/(\d+)/', response.location).group(1)
         assert pull_request2_id != pull_request1_id
-        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (HG_REPO, pull_request2_id)
+        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (base.HG_REPO, pull_request2_id)
         response = response.follow()
         # verify reviewer was added
         response.mustcontain('<input type="hidden" value="%s" name="review_members" />' % regular_user.user_id)
 
         # update without creating new iteration
-        response = self.app.post(url(controller='pullrequests', action='post',
-                                     repo_name=HG_REPO, pull_request_id=pull_request2_id),
+        response = self.app.post(base.url(controller='pullrequests', action='post',
+                                     repo_name=base.HG_REPO, pull_request_id=pull_request2_id),
                                  {
                                   'pullrequest_title': 'Title',
                                   'pullrequest_desc': 'description',
-                                  'owner': TEST_USER_ADMIN_LOGIN,
+                                  'owner': base.TEST_USER_ADMIN_LOGIN,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                   'org_review_members': [admin_user.user_id], # fake - just to get some 'meanwhile' warning ... but it is also added ...
                                   'review_members': [regular_user2.user_id, admin_user.user_id],
                                  },
                                  status=302)
-        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (HG_REPO, pull_request2_id)
+        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (base.HG_REPO, pull_request2_id)
         response = response.follow()
         # verify reviewers were added / removed
         response.mustcontain('Meanwhile, the following reviewers have been added: test_regular')
@@ -141,12 +141,12 @@
         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),
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
                                  {
-                                  'org_repo': HG_REPO,
+                                  'org_repo': base.HG_REPO,
                                   'org_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
@@ -155,34 +155,34 @@
                                 status=302)
         # location is of the form:
         # http://localhost/vcs_test_hg/pull-request/54/_/title
-        m = re.search('/pull-request/(\d+)/', response.location)
+        m = re.search(r'/pull-request/(\d+)/', response.location)
         assert m is not 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),
+        response = self.app.post(base.url(controller='pullrequests', action='post',
+                                     repo_name=base.HG_REPO, pull_request_id=pull_request_id),
                                  {
                                   'updaterev': '4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
-                                  'owner': TEST_USER_ADMIN_LOGIN,
+                                  'owner': base.TEST_USER_ADMIN_LOGIN,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                   'review_members': [str(invalid_user_id)],
                                  },
                                  status=400)
-        response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_id)
+        response.mustcontain('Invalid reviewer &quot;%s&quot; 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),
+        response = self.app.post(base.url(controller='pullrequests', action='create',
+                                     repo_name=base.HG_REPO),
                                  {
-                                  'org_repo': HG_REPO,
+                                  'org_repo': base.HG_REPO,
                                   'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
-                                  'other_repo': HG_REPO,
+                                  'other_repo': base.HG_REPO,
                                   'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
@@ -191,22 +191,22 @@
                                 status=302)
         # location is of the form:
         # http://localhost/vcs_test_hg/pull-request/54/_/title
-        m = re.search('/pull-request/(\d+)/', response.location)
+        m = re.search(r'/pull-request/(\d+)/', response.location)
         assert m is not 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),
+        response = self.app.post(base.url(controller='pullrequests', action='post',
+                                     repo_name=base.HG_REPO, pull_request_id=pull_request_id),
                                  {
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
-                                  'owner': TEST_USER_ADMIN_LOGIN,
+                                  'owner': base.TEST_USER_ADMIN_LOGIN,
                                   '_session_csrf_secret_token': self.session_csrf_secret_token(),
                                   'review_members': [str(invalid_user_id)],
                                  },
                                  status=400)
-        response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_id)
+        response.mustcontain('Invalid reviewer &quot;%s&quot; specified' % invalid_user_id)
 
     def test_iteration_refs(self):
         # Repo graph excerpt:
@@ -226,18 +226,18 @@
 
         # create initial PR
         response = self.app.post(
-            url(controller='pullrequests', action='create', repo_name=HG_REPO),
+            base.url(controller='pullrequests', action='create', repo_name=base.HG_REPO),
             {
-                'org_repo': HG_REPO,
+                'org_repo': base.HG_REPO,
                 'org_ref': 'rev:9e6119747791:9e6119747791ff886a5abe1193a730b6bf874e1c',
-                'other_repo': HG_REPO,
+                'other_repo': base.HG_REPO,
                 'other_ref': 'branch:default:3d1091ee5a533b1f4577ec7d8a226bb315fb1336',
                 'pullrequest_title': 'title',
                 'pullrequest_desc': 'description',
                 '_session_csrf_secret_token': self.session_csrf_secret_token(),
             },
             status=302)
-        pr1_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr1_id = int(re.search(r'/pull-request/(\d+)/', response.location).group(1))
         pr1 = PullRequest.get(pr1_id)
 
         assert pr1.org_ref == 'branch:webvcs:9e6119747791ff886a5abe1193a730b6bf874e1c'
@@ -247,16 +247,16 @@
 
         # create PR 2 (new iteration with same ancestor)
         response = self.app.post(
-            url(controller='pullrequests', action='post', repo_name=HG_REPO, pull_request_id=pr1_id),
+            base.url(controller='pullrequests', action='post', repo_name=base.HG_REPO, pull_request_id=pr1_id),
             {
                 'updaterev': '5ec21f21aafe95220f1fc4843a4a57c378498b71',
                 'pullrequest_title': 'title',
                 'pullrequest_desc': 'description',
-                'owner': TEST_USER_REGULAR_LOGIN,
+                'owner': base.TEST_USER_REGULAR_LOGIN,
                 '_session_csrf_secret_token': self.session_csrf_secret_token(),
              },
              status=302)
-        pr2_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr2_id = int(re.search(r'/pull-request/(\d+)/', response.location).group(1))
         pr1 = PullRequest.get(pr1_id)
         pr2 = PullRequest.get(pr2_id)
 
@@ -269,16 +269,16 @@
 
         # create PR 3 (new iteration with new ancestor)
         response = self.app.post(
-            url(controller='pullrequests', action='post', repo_name=HG_REPO, pull_request_id=pr2_id),
+            base.url(controller='pullrequests', action='post', repo_name=base.HG_REPO, pull_request_id=pr2_id),
             {
                 'updaterev': 'fb95b340e0d03fa51f33c56c991c08077c99303e',
                 'pullrequest_title': 'title',
                 'pullrequest_desc': 'description',
-                'owner': TEST_USER_REGULAR_LOGIN,
+                'owner': base.TEST_USER_REGULAR_LOGIN,
                 '_session_csrf_secret_token': self.session_csrf_secret_token(),
              },
              status=302)
-        pr3_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr3_id = int(re.search(r'/pull-request/(\d+)/', response.location).group(1))
         pr2 = PullRequest.get(pr2_id)
         pr3 = PullRequest.get(pr3_id)
 
@@ -289,7 +289,7 @@
 
 
 @pytest.mark.usefixtures("test_context_fixture") # apply fixture for all test methods
-class TestPullrequestsGetRepoRefs(TestController):
+class TestPullrequestsGetRepoRefs(base.TestController):
 
     def setup_method(self, method):
         self.repo_name = u'main'
--- a/kallithea/tests/functional/test_repo_groups.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_repo_groups.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,16 +1,16 @@
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestRepoGroupsController(TestController):
+class TestRepoGroupsController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url('repos_groups'))
+        response = self.app.get(base.url('repos_groups'))
         response.mustcontain('"records": []')
 
     def test_new(self):
         self.log_user()
-        response = self.app.get(url('new_repos_group'))
+        response = self.app.get(base.url('new_repos_group'))
 
     def test_create(self):
         self.log_user()
@@ -18,14 +18,14 @@
         group_name = 'foo'
 
         # creation with form error
-        response = self.app.post(url('repos_groups'),
+        response = self.app.post(base.url('repos_groups'),
                                          {'group_name': group_name,
                                           '_session_csrf_secret_token': self.session_csrf_secret_token()})
         response.mustcontain('name="group_name" type="text" value="%s"' % group_name)
         response.mustcontain('<!-- for: group_description -->')
 
         # creation
-        response = self.app.post(url('repos_groups'),
+        response = self.app.post(base.url('repos_groups'),
                                          {'group_name': group_name,
                                          'group_description': 'lala',
                                          'parent_group_id': '-1',
@@ -34,18 +34,18 @@
         self.checkSessionFlash(response, 'Created repository group %s' % group_name)
 
         # edit form
-        response = self.app.get(url('edit_repo_group', group_name=group_name))
+        response = self.app.get(base.url('edit_repo_group', group_name=group_name))
         response.mustcontain('>lala<')
 
         # edit with form error
-        response = self.app.post(url('update_repos_group', group_name=group_name),
+        response = self.app.post(base.url('update_repos_group', group_name=group_name),
                                          {'group_name': group_name,
                                           '_session_csrf_secret_token': self.session_csrf_secret_token()})
         response.mustcontain('name="group_name" type="text" value="%s"' % group_name)
         response.mustcontain('<!-- for: group_description -->')
 
         # edit
-        response = self.app.post(url('update_repos_group', group_name=group_name),
+        response = self.app.post(base.url('update_repos_group', group_name=group_name),
                                          {'group_name': group_name,
                                          'group_description': 'lolo',
                                           '_session_csrf_secret_token': self.session_csrf_secret_token()})
@@ -56,22 +56,22 @@
         response.mustcontain('>lolo<')
 
         # listing
-        response = self.app.get(url('repos_groups'))
+        response = self.app.get(base.url('repos_groups'))
         response.mustcontain('raw_name": "%s"' % group_name)
 
         # show
-        response = self.app.get(url('repos_group', group_name=group_name))
+        response = self.app.get(base.url('repos_group', group_name=group_name))
         response.mustcontain('href="/_admin/repo_groups/%s/edit"' % group_name)
 
         # show ignores extra trailing slashes in the URL
-        response = self.app.get(url('repos_group', group_name='%s//' % group_name))
+        response = self.app.get(base.url('repos_group', group_name='%s//' % group_name))
         response.mustcontain('href="/_admin/repo_groups/%s/edit"' % group_name)
 
         # delete
-        response = self.app.post(url('delete_repo_group', group_name=group_name),
+        response = self.app.post(base.url('delete_repo_group', group_name=group_name),
                                  {'_session_csrf_secret_token': self.session_csrf_secret_token()})
         self.checkSessionFlash(response, 'Removed repository group %s' % group_name)
 
     def test_new_by_regular_user(self):
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-        response = self.app.get(url('new_repos_group'), status=403)
+        self.log_user(base.TEST_USER_REGULAR_LOGIN, base.TEST_USER_REGULAR_PASS)
+        response = self.app.get(base.url('new_repos_group'), status=403)
--- a/kallithea/tests/functional/test_search.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_search.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,13 +1,13 @@
 import mock
 
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestSearchController(TestController):
+class TestSearchController(base.TestController):
 
     def test_index(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'))
+        response = self.app.get(base.url(controller='search', action='index'))
 
         response.mustcontain('class="form-control" id="q" name="q" type="text"')
         # Test response...
@@ -20,33 +20,33 @@
             'index_dir': str(tmpdir),
         }
         with mock.patch('kallithea.controllers.search.config', config_mock):
-            response = self.app.get(url(controller='search', action='index'),
-                                    {'q': HG_REPO})
+            response = self.app.get(base.url(controller='search', action='index'),
+                                    {'q': base.HG_REPO})
             response.mustcontain('The server has no search index.')
 
     def test_normal_search(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': 'def repo'})
         response.mustcontain('58 results')
 
     def test_repo_search(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
-                                {'q': 'repository:%s def test' % HG_REPO})
+        response = self.app.get(base.url(controller='search', action='index'),
+                                {'q': 'repository:%s def test' % base.HG_REPO})
 
         response.mustcontain('18 results')
 
     def test_search_last(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': 'last:t', 'type': 'commit'})
 
         response.mustcontain('2 results')
 
     def test_search_commit_message(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                     {'q': 'bother to ask where to fetch repo during tests',
                      'type': 'commit'})
 
@@ -56,8 +56,8 @@
 
     def test_search_commit_message_hg_repo(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index',
-                                    repo_name=HG_REPO),
+        response = self.app.get(base.url(controller='search', action='index',
+                                    repo_name=base.HG_REPO),
                     {'q': 'bother to ask where to fetch repo during tests',
                      'type': 'commit'})
 
@@ -66,7 +66,7 @@
 
     def test_search_commit_changed_file(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': 'changed:tests/utils.py',
                                  'type': 'commit'})
 
@@ -74,7 +74,7 @@
 
     def test_search_commit_changed_files_get_commit(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': 'changed:vcs/utils/archivers.py',
                                  'type': 'commit'})
 
@@ -90,7 +90,7 @@
 
     def test_search_commit_added_file(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': 'added:README.rst',
                                  'type': 'commit'})
 
@@ -102,7 +102,7 @@
 
     def test_search_author(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                     {'q': 'author:marcin@python-blog.com raw_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545',
                      'type': 'commit'})
 
@@ -110,7 +110,7 @@
 
     def test_search_file_name(self):
         self.log_user()
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                     {'q': 'README.rst', 'type': 'path'})
 
         response.mustcontain('2 results')
--- a/kallithea/tests/functional/test_search_indexing.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_search_indexing.py	Thu Feb 06 01:19:23 2020 +0100
@@ -5,7 +5,7 @@
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.repo_group import RepoGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture, create_test_index
 
 
@@ -66,10 +66,10 @@
         # (FYI, ENOMEM occurs at forking "git" with python 2.7.3,
         # Linux 3.2.78-1 x86_64, 3GB memory, and no ulimit
         # configuration for memory)
-        create_test_index(TESTS_TMP_PATH, CONFIG, full_index=full_index)
+        create_test_index(base.TESTS_TMP_PATH, CONFIG, full_index=full_index)
 
 
-class TestSearchControllerIndexing(TestController):
+class TestSearchControllerIndexing(base.TestController):
     @classmethod
     def setup_class(cls):
         for reponame, init_or_fork, groupname in repos:
@@ -108,7 +108,7 @@
 
         rebuild_index(full_index=True) # rebuild fully for subsequent tests
 
-    @parametrize('reponame', [
+    @base.parametrize('reponame', [
         (u'indexing_test'),
         (u'indexing_test-fork'),
         (u'group/indexing_test'),
@@ -116,7 +116,7 @@
         (u'*-fork'),
         (u'group/*'),
     ])
-    @parametrize('searchtype,query,hit', [
+    @base.parametrize('searchtype,query,hit', [
         ('content', 'this_should_be_unique_content', 1),
         ('commit', 'this_should_be_unique_commit_log', 1),
         ('path', 'this_should_be_unique_filename.txt', 1),
@@ -125,17 +125,17 @@
         self.log_user()
 
         q = 'repository:%s %s' % (reponame, query)
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': q, 'type': searchtype})
         response.mustcontain('>%d results' % hit)
 
-    @parametrize('reponame', [
+    @base.parametrize('reponame', [
         (u'indexing_test'),
         (u'indexing_test-fork'),
         (u'group/indexing_test'),
         (u'this-is-it'),
     ])
-    @parametrize('searchtype,query,hit', [
+    @base.parametrize('searchtype,query,hit', [
         ('content', 'this_should_be_unique_content', 1),
         ('commit', 'this_should_be_unique_commit_log', 1),
         ('path', 'this_should_be_unique_filename.txt', 1),
@@ -143,12 +143,12 @@
     def test_searching_under_repository(self, reponame, searchtype, query, hit):
         self.log_user()
 
-        response = self.app.get(url(controller='search', action='index',
+        response = self.app.get(base.url(controller='search', action='index',
                                     repo_name=reponame),
                                 {'q': query, 'type': searchtype})
         response.mustcontain('>%d results' % hit)
 
-    @parametrize('searchtype,query,hit', [
+    @base.parametrize('searchtype,query,hit', [
         ('content', 'path:this/is/it def test', 1),
         ('commit', 'added:this/is/it bother to ask where', 1),
         # this condition matches against files below, because
@@ -161,12 +161,12 @@
         ('path', 'extension:us', 1),
     ])
     def test_filename_stopword(self, searchtype, query, hit):
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': query, 'type': searchtype})
 
         response.mustcontain('>%d results' % hit)
 
-    @parametrize('searchtype,query,hit', [
+    @base.parametrize('searchtype,query,hit', [
         # matching against both 2 files
         ('content', 'owner:"this is it"', 0),
         ('content', 'owner:this-is-it', 0),
@@ -182,7 +182,7 @@
         ('commit', 'author:"this-is-it"', 1),
     ])
     def test_mailaddr_stopword(self, searchtype, query, hit):
-        response = self.app.get(url(controller='search', action='index'),
+        response = self.app.get(base.url(controller='search', action='index'),
                                 {'q': query, 'type': searchtype})
 
         response.mustcontain('>%d results' % hit)
--- a/kallithea/tests/functional/test_summary.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/functional/test_summary.py	Thu Feb 06 01:19:23 2020 +0100
@@ -18,7 +18,7 @@
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.scm import ScmModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -32,14 +32,14 @@
         )
 
 
-class TestSummaryController(TestController):
+class TestSummaryController(base.TestController):
 
     def test_index_hg(self, custom_settings):
         self.log_user()
-        ID = Repository.get_by_repo_name(HG_REPO).repo_id
-        response = self.app.get(url(controller='summary',
+        ID = Repository.get_by_repo_name(base.HG_REPO).repo_id
+        response = self.app.get(base.url(controller='summary',
                                     action='index',
-                                    repo_name=HG_REPO))
+                                    repo_name=base.HG_REPO))
 
         # repo type
         response.mustcontain(
@@ -52,24 +52,24 @@
         # clone URLs
         response.mustcontain(
             '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/%s"/>''' %
-            (TEST_USER_ADMIN_LOGIN, HG_REPO)
+            (base.TEST_USER_ADMIN_LOGIN, base.HG_REPO)
         )
         response.mustcontain(
             '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/_%s"/>''' %
-            (TEST_USER_ADMIN_LOGIN, ID)
+            (base.TEST_USER_ADMIN_LOGIN, ID)
         )
         response.mustcontain(
             '''<input id="ssh_url" class="form-control" size="80" readonly="readonly" value="ssh://ssh_user@ssh_hostname/%s"/>''' %
-            (HG_REPO)
+            (base.HG_REPO)
         )
 
 
     def test_index_git(self, custom_settings):
         self.log_user()
-        ID = Repository.get_by_repo_name(GIT_REPO).repo_id
-        response = self.app.get(url(controller='summary',
+        ID = Repository.get_by_repo_name(base.GIT_REPO).repo_id
+        response = self.app.get(base.url(controller='summary',
                                     action='index',
-                                    repo_name=GIT_REPO))
+                                    repo_name=base.GIT_REPO))
 
         # repo type
         response.mustcontain(
@@ -82,21 +82,21 @@
         # clone URLs
         response.mustcontain(
             '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/%s"/>''' %
-            (TEST_USER_ADMIN_LOGIN, GIT_REPO)
+            (base.TEST_USER_ADMIN_LOGIN, base.GIT_REPO)
         )
         response.mustcontain(
             '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/_%s"/>''' %
-            (TEST_USER_ADMIN_LOGIN, ID)
+            (base.TEST_USER_ADMIN_LOGIN, ID)
         )
         response.mustcontain(
             '''<input id="ssh_url" class="form-control" size="80" readonly="readonly" value="ssh://ssh_user@ssh_hostname/%s"/>''' %
-            (GIT_REPO)
+            (base.GIT_REPO)
         )
 
     def test_index_by_id_hg(self):
         self.log_user()
-        ID = Repository.get_by_repo_name(HG_REPO).repo_id
-        response = self.app.get(url(controller='summary',
+        ID = Repository.get_by_repo_name(base.HG_REPO).repo_id
+        response = self.app.get(base.url(controller='summary',
                                     action='index',
                                     repo_name='_%s' % ID))
 
@@ -112,7 +112,7 @@
     def test_index_by_repo_having_id_path_in_name_hg(self):
         self.log_user()
         fixture.create_repo(name=u'repo_1')
-        response = self.app.get(url(controller='summary',
+        response = self.app.get(base.url(controller='summary',
                                     action='index',
                                     repo_name='repo_1'))
 
@@ -124,8 +124,8 @@
 
     def test_index_by_id_git(self):
         self.log_user()
-        ID = Repository.get_by_repo_name(GIT_REPO).repo_id
-        response = self.app.get(url(controller='summary',
+        ID = Repository.get_by_repo_name(base.GIT_REPO).repo_id
+        response = self.app.get(base.url(controller='summary',
                                     action='index',
                                     repo_name='_%s' % ID))
 
@@ -146,14 +146,14 @@
     def test_index_trending(self):
         self.log_user()
         # codes stats
-        self._enable_stats(HG_REPO)
+        self._enable_stats(base.HG_REPO)
 
-        ScmModel().mark_for_invalidation(HG_REPO)
+        ScmModel().mark_for_invalidation(base.HG_REPO)
         # generate statistics first
-        response = self.app.get(url(controller='summary', action='statistics',
-                                    repo_name=HG_REPO))
-        response = self.app.get(url(controller='summary', action='index',
-                                    repo_name=HG_REPO))
+        response = self.app.get(base.url(controller='summary', action='statistics',
+                                    repo_name=base.HG_REPO))
+        response = self.app.get(base.url(controller='summary', action='index',
+                                    repo_name=base.HG_REPO))
         response.mustcontain(
             '[["py", {"count": 68, "desc": ["Python"]}], '
             '["rst", {"count": 16, "desc": ["Rst"]}], '
@@ -170,23 +170,23 @@
     def test_index_statistics(self):
         self.log_user()
         # codes stats
-        self._enable_stats(HG_REPO)
+        self._enable_stats(base.HG_REPO)
 
-        ScmModel().mark_for_invalidation(HG_REPO)
-        response = self.app.get(url(controller='summary', action='statistics',
-                                    repo_name=HG_REPO))
+        ScmModel().mark_for_invalidation(base.HG_REPO)
+        response = self.app.get(base.url(controller='summary', action='statistics',
+                                    repo_name=base.HG_REPO))
 
     def test_index_trending_git(self):
         self.log_user()
         # codes stats
-        self._enable_stats(GIT_REPO)
+        self._enable_stats(base.GIT_REPO)
 
-        ScmModel().mark_for_invalidation(GIT_REPO)
+        ScmModel().mark_for_invalidation(base.GIT_REPO)
         # generate statistics first
-        response = self.app.get(url(controller='summary', action='statistics',
-                                    repo_name=GIT_REPO))
-        response = self.app.get(url(controller='summary', action='index',
-                                    repo_name=GIT_REPO))
+        response = self.app.get(base.url(controller='summary', action='statistics',
+                                    repo_name=base.GIT_REPO))
+        response = self.app.get(base.url(controller='summary', action='index',
+                                    repo_name=base.GIT_REPO))
         response.mustcontain(
             '[["py", {"count": 68, "desc": ["Python"]}], '
             '["rst", {"count": 16, "desc": ["Rst"]}], '
@@ -203,8 +203,8 @@
     def test_index_statistics_git(self):
         self.log_user()
         # codes stats
-        self._enable_stats(GIT_REPO)
+        self._enable_stats(base.GIT_REPO)
 
-        ScmModel().mark_for_invalidation(GIT_REPO)
-        response = self.app.get(url(controller='summary', action='statistics',
-                                    repo_name=GIT_REPO))
+        ScmModel().mark_for_invalidation(base.GIT_REPO)
+        response = self.app.get(base.url(controller='summary', action='statistics',
+                                    repo_name=base.GIT_REPO))
--- a/kallithea/tests/models/test_changeset_status.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_changeset_status.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,6 +1,6 @@
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.db import ChangesetStatus as CS
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
 class CSM(object): # ChangesetStatusMock
@@ -9,12 +9,12 @@
         self.status = status
 
 
-class TestChangesetStatusCalculation(TestController):
+class TestChangesetStatusCalculation(base.TestController):
 
     def setup_method(self, method):
         self.m = ChangesetStatusModel()
 
-    @parametrize('name,expected_result,statuses', [
+    @base.parametrize('name,expected_result,statuses', [
         ('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)]),
--- a/kallithea/tests/models/test_comments.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_comments.py	Thu Feb 06 01:19:23 2020 +0100
@@ -3,10 +3,10 @@
 
 from kallithea.model.comment import ChangesetCommentsModel
 from kallithea.model.db import Repository
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestComments(TestController):
+class TestComments(base.TestController):
 
     def _check_comment_count(self, repo_id, revision,
             expected_len_comments, expected_len_inline_comments,
@@ -23,7 +23,7 @@
 
     def test_create_delete_general_comment(self):
         with test_context(self.app):
-            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
             revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
 
             self._check_comment_count(repo_id, revision,
@@ -32,8 +32,8 @@
             text = u'a comment'
             new_comment = ChangesetCommentsModel().create(
                     text=text,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     send_email=False)
 
@@ -47,7 +47,7 @@
 
     def test_create_delete_inline_comment(self):
         with test_context(self.app):
-            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
             revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
 
             self._check_comment_count(repo_id, revision,
@@ -58,8 +58,8 @@
             line_no = u'n50'
             new_comment = ChangesetCommentsModel().create(
                     text=text,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path,
                     line_no=line_no,
@@ -81,7 +81,7 @@
 
     def test_create_delete_multiple_inline_comments(self):
         with test_context(self.app):
-            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
             revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
 
             self._check_comment_count(repo_id, revision,
@@ -92,8 +92,8 @@
             line_no = u'n50'
             new_comment = ChangesetCommentsModel().create(
                     text=text,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path,
                     line_no=line_no,
@@ -103,8 +103,8 @@
             line_no2 = u'o41'
             new_comment2 = ChangesetCommentsModel().create(
                     text=text2,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path,
                     line_no=line_no2,
@@ -115,8 +115,8 @@
             line_no3 = u'n159'
             new_comment3 = ChangesetCommentsModel().create(
                     text=text3,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path3,
                     line_no=line_no3,
@@ -126,15 +126,15 @@
                     expected_len_comments=0, expected_len_inline_comments=2)
             # inline_comments is a list of tuples (file_path, dict)
             # where the dict keys are line numbers and values are lists of comments
-            assert inline_comments[1][0] == f_path
-            assert len(inline_comments[1][1]) == 2
-            assert inline_comments[1][1][line_no][0].text == text
-            assert inline_comments[1][1][line_no2][0].text == text2
+            assert inline_comments[0][0] == f_path
+            assert len(inline_comments[0][1]) == 2
+            assert inline_comments[0][1][line_no][0].text == text
+            assert inline_comments[0][1][line_no2][0].text == text2
 
-            assert inline_comments[0][0] == f_path3
-            assert len(inline_comments[0][1]) == 1
-            assert line_no3 in inline_comments[0][1]
-            assert inline_comments[0][1][line_no3][0].text == text3
+            assert inline_comments[1][0] == f_path3
+            assert len(inline_comments[1][1]) == 1
+            assert line_no3 in inline_comments[1][1]
+            assert inline_comments[1][1][line_no3][0].text == text3
 
             # now delete only one comment
             ChangesetCommentsModel().delete(new_comment2)
@@ -143,14 +143,14 @@
                     expected_len_comments=0, expected_len_inline_comments=2)
             # inline_comments is a list of tuples (file_path, dict)
             # where the dict keys are line numbers and values are lists of comments
-            assert inline_comments[1][0] == f_path
-            assert len(inline_comments[1][1]) == 1
-            assert inline_comments[1][1][line_no][0].text == text
+            assert inline_comments[0][0] == f_path
+            assert len(inline_comments[0][1]) == 1
+            assert inline_comments[0][1][line_no][0].text == text
 
-            assert inline_comments[0][0] == f_path3
-            assert len(inline_comments[0][1]) == 1
-            assert line_no3 in inline_comments[0][1]
-            assert inline_comments[0][1][line_no3][0].text == text3
+            assert inline_comments[1][0] == f_path3
+            assert len(inline_comments[1][1]) == 1
+            assert line_no3 in inline_comments[1][1]
+            assert inline_comments[1][1][line_no3][0].text == text3
 
             # now delete all others
             ChangesetCommentsModel().delete(new_comment)
@@ -161,7 +161,7 @@
 
     def test_selective_retrieval_of_inline_comments(self):
         with test_context(self.app):
-            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
             revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
 
             self._check_comment_count(repo_id, revision,
@@ -172,8 +172,8 @@
             line_no = u'n50'
             new_comment = ChangesetCommentsModel().create(
                     text=text,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path,
                     line_no=line_no,
@@ -183,8 +183,8 @@
             line_no2 = u'o41'
             new_comment2 = ChangesetCommentsModel().create(
                     text=text2,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path,
                     line_no=line_no2,
@@ -195,8 +195,8 @@
             line_no3 = u'n159'
             new_comment3 = ChangesetCommentsModel().create(
                     text=text3,
-                    repo=HG_REPO,
-                    author=TEST_USER_REGULAR_LOGIN,
+                    repo=base.HG_REPO,
+                    author=base.TEST_USER_REGULAR_LOGIN,
                     revision=revision,
                     f_path=f_path3,
                     line_no=line_no3,
--- a/kallithea/tests/models/test_diff_parsers.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_diff_parsers.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,5 +1,5 @@
 from kallithea.lib.diffs import BIN_FILENODE, CHMOD_FILENODE, COPIED_FILENODE, DEL_FILENODE, MOD_FILENODE, NEW_FILENODE, RENAMED_FILENODE, DiffProcessor
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -271,9 +271,9 @@
 }
 
 
-class TestDiffLib(TestController):
+class TestDiffLib(base.TestController):
 
-    @parametrize('diff_fixture', DIFF_FIXTURES)
+    @base.parametrize('diff_fixture', DIFF_FIXTURES)
     def test_diff(self, diff_fixture):
         raw_diff = fixture.load_resource(diff_fixture, strip=False)
         vcs = 'hg'
@@ -295,20 +295,20 @@
             l.append('%(action)-7s %(new_lineno)3s %(old_lineno)3s %(line)r\n' % d)
         s = ''.join(l)
         assert s == r'''
-context ... ... u'@@ -51,6 +51,13 @@\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>#define MAX_STEPS (48)\n'
-add      60     u'<u>\t</u><u class="cr"></u>\n'
-add      61     u'<u>\t</u>#define MAX_STEPS (64)<u class="cr"></u>\n'
-unmod    62  55 u'\n'
-del          56 u'<u>\t</u>#define MIN_STEPS (<del>48</del>)\n'
-add      63     u'<u>\t</u>#define MIN_STEPS (<ins>42</ins>)\n'
+context ... ... '@@ -51,6 +51,13 @@\n'
+unmod    51  51 '<u>\t</u>begin();\n'
+unmod    52  52 '<u>\t</u>\n'
+add      53     '<u>\t</u>int foo;<u class="cr"></u>\n'
+add      54     '<u>\t</u>int bar; <u class="cr"></u>\n'
+add      55     '<u>\t</u>int baz;<u>\t</u><u class="cr"></u>\n'
+add      56     '<u>\t</u>int space; <i></i>'
+add      57     '<u>\t</u>int tab;<u>\t</u>\n'
+add      58     '<u>\t</u>\n'
+unmod    59  53 ' <i></i>'
+del          54 '<u>\t</u>#define MAX_STEPS (48)\n'
+add      60     '<u>\t</u><u class="cr"></u>\n'
+add      61     '<u>\t</u>#define MAX_STEPS (64)<u class="cr"></u>\n'
+unmod    62  55 '\n'
+del          56 '<u>\t</u>#define MIN_STEPS (<del>48</del>)\n'
+add      63     '<u>\t</u>#define MIN_STEPS (<ins>42</ins>)\n'
 '''
--- a/kallithea/tests/models/test_notifications.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_notifications.py	Thu Feb 06 01:19:23 2020 +0100
@@ -11,10 +11,10 @@
 from kallithea.model.meta import Session
 from kallithea.model.notification import EmailNotificationModel, NotificationModel
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-class TestNotifications(TestController):
+class TestNotifications(base.TestController):
 
     def setup_method(self, method):
         Session.remove()
--- a/kallithea/tests/models/test_permissions.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_permissions.py	Thu Feb 06 01:19:23 2020 +0100
@@ -6,14 +6,14 @@
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.user import UserModel
 from kallithea.model.user_group import UserGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestPermissions(TestController):
+class TestPermissions(base.TestController):
 
     @classmethod
     def setup_class(cls):
@@ -71,33 +71,33 @@
             'repositories_groups': {},
             'global': set(['hg.create.repository', 'repository.read',
                            'hg.register.manual_activate']),
-            'repositories': {HG_REPO: 'repository.read'}
+            'repositories': {base.HG_REPO: 'repository.read'}
         }
-        assert u1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == perms['repositories'][base.HG_REPO]
         new_perm = 'repository.write'
-        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
+        RepoModel().grant_user_permission(repo=base.HG_REPO, user=self.u1,
                                           perm=new_perm)
         Session().commit()
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == new_perm
 
     def test_default_admin_perms_set(self):
         a1_auth = AuthUser(user_id=self.a1.user_id)
         perms = {
             'repositories_groups': {},
             'global': set(['hg.admin', 'hg.create.write_on_repogroup.true']),
-            'repositories': {HG_REPO: 'repository.admin'}
+            'repositories': {base.HG_REPO: 'repository.admin'}
         }
-        assert a1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert a1_auth.permissions['repositories'][base.HG_REPO] == perms['repositories'][base.HG_REPO]
         new_perm = 'repository.write'
-        RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1,
+        RepoModel().grant_user_permission(repo=base.HG_REPO, user=self.a1,
                                           perm=new_perm)
         Session().commit()
         # cannot really downgrade admins permissions !? they still gets set as
         # admin !
         u1_auth = AuthUser(user_id=self.a1.user_id)
-        assert u1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == perms['repositories'][base.HG_REPO]
 
     def test_default_group_perms(self):
         self.g1 = fixture.create_repo_group(u'test1', skip_if_exists=True)
@@ -106,9 +106,9 @@
         perms = {
             'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
             'global': set(Permission.DEFAULT_USER_PERMISSIONS),
-            'repositories': {HG_REPO: 'repository.read'}
+            'repositories': {base.HG_REPO: 'repository.read'}
         }
-        assert u1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == perms['repositories'][base.HG_REPO]
         assert u1_auth.permissions['repositories_groups'] == perms['repositories_groups']
         assert u1_auth.permissions['global'] == perms['global']
 
@@ -119,10 +119,10 @@
         perms = {
             'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
             'global': set(['hg.admin', 'hg.create.write_on_repogroup.true']),
-            'repositories': {HG_REPO: 'repository.admin'}
+            'repositories': {base.HG_REPO: 'repository.admin'}
         }
 
-        assert a1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert a1_auth.permissions['repositories'][base.HG_REPO] == perms['repositories'][base.HG_REPO]
         assert a1_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_propagated_permission_from_users_group_by_explicit_perms_exist(self):
@@ -131,19 +131,19 @@
         UserGroupModel().add_user_to_group(self.ug1, self.u1)
 
         # set user permission none
-        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm='repository.none')
+        RepoModel().grant_user_permission(repo=base.HG_REPO, user=self.u1, perm='repository.none')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        assert u1_auth.permissions['repositories'][HG_REPO] == 'repository.read' # inherit from default user
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == 'repository.read' # inherit from default user
 
         # grant perm for group this should override permission from user
-        RepoModel().grant_user_group_permission(repo=HG_REPO,
+        RepoModel().grant_user_group_permission(repo=base.HG_REPO,
                                                  group_name=self.ug1,
                                                  perm='repository.write')
 
         # verify that user group permissions win
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        assert u1_auth.permissions['repositories'][HG_REPO] == 'repository.write'
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == 'repository.write'
 
     def test_propagated_permission_from_users_group(self):
         # make group
@@ -152,7 +152,7 @@
 
         # grant perm for group this should override default permission from user
         new_perm_gr = 'repository.write'
-        RepoModel().grant_user_group_permission(repo=HG_REPO,
+        RepoModel().grant_user_group_permission(repo=base.HG_REPO,
                                                  group_name=self.ug1,
                                                  perm=new_perm_gr)
         # check perms
@@ -161,9 +161,9 @@
             'repositories_groups': {},
             'global': set(['hg.create.repository', 'repository.read',
                            'hg.register.manual_activate']),
-            'repositories': {HG_REPO: 'repository.read'}
+            'repositories': {base.HG_REPO: 'repository.read'}
         }
-        assert u3_auth.permissions['repositories'][HG_REPO] == new_perm_gr
+        assert u3_auth.permissions['repositories'][base.HG_REPO] == new_perm_gr
         assert u3_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_propagated_permission_from_users_group_lower_weight(self):
@@ -174,16 +174,16 @@
 
         # set permission to lower
         new_perm_h = 'repository.write'
-        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
+        RepoModel().grant_user_permission(repo=base.HG_REPO, user=self.u1,
                                           perm=new_perm_h)
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm_h
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == new_perm_h
 
         # grant perm for group this should NOT override permission from user
         # since it's lower than granted
         new_perm_l = 'repository.read'
-        RepoModel().grant_user_group_permission(repo=HG_REPO,
+        RepoModel().grant_user_group_permission(repo=base.HG_REPO,
                                                  group_name=self.ug1,
                                                  perm=new_perm_l)
         # check perms
@@ -192,9 +192,9 @@
             'repositories_groups': {},
             'global': set(['hg.create.repository', 'repository.read',
                            'hg.register.manual_activate']),
-            'repositories': {HG_REPO: 'repository.write'}
+            'repositories': {base.HG_REPO: 'repository.write'}
         }
-        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm_h
+        assert u1_auth.permissions['repositories'][base.HG_REPO] == new_perm_h
         assert u1_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_repo_in_group_permissions(self):
@@ -641,7 +641,7 @@
         PermissionModel().create_default_permissions(user=self.u1)
         self._test_def_perm_equal(user=self.u1)
 
-    @parametrize('perm,modify_to', [
+    @base.parametrize('perm,modify_to', [
         ('repository.read', 'repository.none'),
         ('group.read', 'group.none'),
         ('usergroup.read', 'usergroup.none'),
--- a/kallithea/tests/models/test_repo_groups.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_repo_groups.py	Thu Feb 06 01:19:23 2020 +0100
@@ -7,7 +7,7 @@
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.repo_group import RepoGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -34,7 +34,7 @@
     return r
 
 
-class TestRepoGroups(TestController):
+class TestRepoGroups(base.TestController):
 
     def setup_method(self, method):
         self.g1 = fixture.create_repo_group(u'test1', skip_if_exists=True)
@@ -48,7 +48,7 @@
         """
         Checks the path for existence !
         """
-        path = [TESTS_TMP_PATH] + list(path)
+        path = [base.TESTS_TMP_PATH] + list(path)
         path = os.path.join(*path)
         return os.path.isdir(path)
 
--- a/kallithea/tests/models/test_repos.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_repos.py	Thu Feb 06 01:19:23 2020 +0100
@@ -4,14 +4,14 @@
 from kallithea.model.db import Repository
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestRepos(TestController):
+class TestRepos(base.TestController):
 
     def teardown_method(self, method):
         Session.remove()
--- a/kallithea/tests/models/test_settings.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_settings.py	Thu Feb 06 01:19:23 2020 +0100
@@ -39,8 +39,8 @@
         # Assign back setting value.
         setting.app_settings_value = setting.app_settings_value
         # Quirk: value is stringified on write and listified on read.
-        assert setting.app_settings_value == ["[u'spam']"]
+        assert setting.app_settings_value == ["['spam']"]
         setting.app_settings_value = setting.app_settings_value
-        assert setting.app_settings_value == ["[u\"[u'spam']\"]"]
+        assert setting.app_settings_value == ["[\"['spam']\"]"]
     finally:
         Session().delete(setting)
--- a/kallithea/tests/models/test_user_groups.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_user_groups.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,14 +1,14 @@
 from kallithea.model.db import User, UserGroup
 from kallithea.model.meta import Session
 from kallithea.model.user_group import UserGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestUserGroups(TestController):
+class TestUserGroups(base.TestController):
 
     def teardown_method(self, method):
         # delete all groups
@@ -16,7 +16,7 @@
             fixture.destroy_user_group(gr)
         Session().commit()
 
-    @parametrize('pre_existing,regular_should_be,external_should_be,groups,expected', [
+    @base.parametrize('pre_existing,regular_should_be,external_should_be,groups,expected', [
         ([], [], [], [], []),
         ([], [u'regular'], [], [], [u'regular']),  # no changes of regular
         ([u'some_other'], [], [], [u'some_other'], []),   # not added to regular group
@@ -32,7 +32,7 @@
             fixture.destroy_user_group(gr)
         Session().commit()
 
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         for gr in pre_existing:
             gr = fixture.create_user_group(gr)
         Session().commit()
@@ -54,6 +54,6 @@
         UserGroupModel().enforce_groups(user, groups, 'container')
         Session().commit()
 
-        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        user = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
         in_groups = user.group_member
-        assert expected == [x.users_group.users_group_name for x in in_groups]
+        assert sorted(expected) == sorted(x.users_group.users_group_name for x in in_groups)
--- a/kallithea/tests/models/test_users.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/models/test_users.py	Thu Feb 06 01:19:23 2020 +0100
@@ -4,14 +4,14 @@
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
 from kallithea.model.user_group import UserGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
 fixture = Fixture()
 
 
-class TestUser(TestController):
+class TestUser(base.TestController):
 
     @classmethod
     def setup_class(cls):
@@ -101,7 +101,7 @@
         Session().commit()
 
 
-class TestUsers(TestController):
+class TestUsers(base.TestController):
 
     def setup_method(self, method):
         self.u1 = UserModel().create_or_update(username=u'u1',
--- a/kallithea/tests/other/test_libs.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/other/test_libs.py	Thu Feb 06 01:19:23 2020 +0100
@@ -31,9 +31,9 @@
 import mock
 from tg.util.webtest import test_context
 
-from kallithea.lib.utils2 import AttributeDict
+from kallithea.lib.utils2 import AttributeDict, safe_bytes
 from kallithea.model.db import Repository
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
 proto = 'http'
@@ -91,19 +91,19 @@
         return self.current_url % kwargs
 
 
-class TestLibs(TestController):
+class TestLibs(base.TestController):
 
-    @parametrize('test_url,expected,expected_creds', TEST_URLS)
+    @base.parametrize('test_url,expected,expected_creds', TEST_URLS)
     def test_uri_filter(self, test_url, expected, expected_creds):
         from kallithea.lib.utils2 import uri_filter
         assert uri_filter(test_url) == expected
 
-    @parametrize('test_url,expected,expected_creds', TEST_URLS)
+    @base.parametrize('test_url,expected,expected_creds', TEST_URLS)
     def test_credentials_filter(self, test_url, expected, expected_creds):
         from kallithea.lib.utils2 import credentials_filter
         assert credentials_filter(test_url) == expected_creds
 
-    @parametrize('str_bool,expected', [
+    @base.parametrize('str_bool,expected', [
                            ('t', True),
                            ('true', True),
                            ('y', True),
@@ -141,7 +141,7 @@
             'marian.user', 'marco-polo', 'marco_polo', 'world'])
         assert expected == set(extract_mentioned_usernames(sample))
 
-    @parametrize('age_args,expected', [
+    @base.parametrize('age_args,expected', [
         (dict(), u'just now'),
         (dict(seconds= -1), u'1 second ago'),
         (dict(seconds= -60 * 2), u'2 minutes ago'),
@@ -165,7 +165,7 @@
             delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
             assert age(n + delt(**age_args), now=n) == expected
 
-    @parametrize('age_args,expected', [
+    @base.parametrize('age_args,expected', [
         (dict(), u'just now'),
         (dict(seconds= -1), u'1 second ago'),
         (dict(seconds= -60 * 2), u'2 minutes ago'),
@@ -190,7 +190,7 @@
             delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
             assert age(n + delt(**age_args), show_short_version=True, now=n) == expected
 
-    @parametrize('age_args,expected', [
+    @base.parametrize('age_args,expected', [
         (dict(), u'just now'),
         (dict(seconds=1), u'in 1 second'),
         (dict(seconds=60 * 2), u'in 2 minutes'),
@@ -227,7 +227,7 @@
 
     def test_alternative_gravatar(self):
         from kallithea.lib.helpers import gravatar_url
-        _md5 = lambda s: hashlib.md5(s).hexdigest()
+        _md5 = lambda s: hashlib.md5(safe_bytes(s)).hexdigest()
 
         # mock tg.tmpl_context
         def fake_tmpl_context(_url):
@@ -270,7 +270,7 @@
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'https://example.com/%s/%s' % (_md5(em), 24)
 
-    @parametrize('clone_uri_tmpl,repo_name,username,prefix,expected', [
+    @base.parametrize('clone_uri_tmpl,repo_name,username,prefix,expected', [
         (Repository.DEFAULT_CLONE_URI, 'group/repo1', None, '', 'http://vps1:8000/group/repo1'),
         (Repository.DEFAULT_CLONE_URI, 'group/repo1', 'username', '', 'http://username@vps1:8000/group/repo1'),
         (Repository.DEFAULT_CLONE_URI, 'group/repo1', None, '/prefix', 'http://vps1:8000/prefix/group/repo1'),
@@ -307,7 +307,7 @@
             return tmpl % (url_ or '/repo_name/changeset/%s' % _url, _url)
         return url_pattern.sub(url_func, text)
 
-    @parametrize('sample,expected', [
+    @base.parametrize('sample,expected', [
       ("",
        ""),
       ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
@@ -341,7 +341,7 @@
             from kallithea.lib.helpers import urlify_text
             assert urlify_text(sample, 'repo_name') == expected
 
-    @parametrize('sample,expected,url_', [
+    @base.parametrize('sample,expected,url_', [
       ("",
        "",
        ""),
@@ -396,7 +396,7 @@
             from kallithea.lib.helpers import urlify_text
             assert urlify_text(sample, 'repo_name', stylize=True) == expected
 
-    @parametrize('sample,expected', [
+    @base.parametrize('sample,expected', [
       ("deadbeefcafe @mention, and http://foo.bar/ yo",
        """<a class="changeset_hash" href="/repo_name/changeset/deadbeefcafe">deadbeefcafe</a>"""
        """<a class="message-link" href="#the-link"> <b>@mention</b>, and </a>"""
@@ -409,7 +409,7 @@
             from kallithea.lib.helpers import urlify_text
             assert urlify_text(sample, 'repo_name', link_='#the-link') == expected
 
-    @parametrize('issue_pat,issue_server,issue_sub,sample,expected', [
+    @base.parametrize('issue_pat,issue_server,issue_sub,sample,expected', [
         (r'#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
             'issue #123 and issue#456',
             """issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a> and """
@@ -482,7 +482,7 @@
             """empty issue_sub <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">$123</a> and """
             """issue$456"""),
         # named groups
-        (r'(PR|pullrequest|pull request) ?(?P<sitecode>BRU|CPH|BER)-(?P<id>\d+)', 'http://foo/\g<sitecode>/pullrequest/\g<id>/', 'PR-\g<sitecode>-\g<id>',
+        (r'(PR|pullrequest|pull request) ?(?P<sitecode>BRU|CPH|BER)-(?P<id>\d+)', r'http://foo/\g<sitecode>/pullrequest/\g<id>/', r'PR-\g<sitecode>-\g<id>',
             'pullrequest CPH-789 is similar to PRBRU-747',
             """<a class="issue-tracker-link" href="http://foo/CPH/pullrequest/789/">PR-CPH-789</a> is similar to """
             """<a class="issue-tracker-link" href="http://foo/BRU/pullrequest/747/">PR-BRU-747</a>"""),
@@ -500,7 +500,7 @@
             with mock.patch('kallithea.CONFIG', config_stub):
                 assert urlify_text(sample, 'repo_name') == expected
 
-    @parametrize('sample,expected', [
+    @base.parametrize('sample,expected', [
         ('abc X5', 'abc <a class="issue-tracker-link" href="http://main/repo_name/main/5/">#5</a>'),
         ('abc pullrequest #6 xyz', 'abc <a class="issue-tracker-link" href="http://pr/repo_name/pr/6">PR#6</a> xyz'),
         ('pull request7 #', '<a class="issue-tracker-link" href="http://pr/repo_name/pr/7">PR#7</a> #'),
@@ -512,28 +512,28 @@
     def test_urlify_issues_multiple_issue_patterns(self, sample, expected):
         from kallithea.lib.helpers import urlify_text
         config_stub = {
-            'sqlalchemy.url': 'foo',
-            'issue_pat': 'X(\d+)',
-            'issue_server_link': 'http://main/{repo}/main/\\1/',
-            'issue_sub': '#\\1',
-            'issue_pat_pr': '(?:pullrequest|pull request|PR|pr) ?#?(\d+)',
-            'issue_server_link_pr': 'http://pr/{repo}/pr/\\1',
-            'issue_sub_pr': 'PR#\\1',
-            'issue_pat_bug': '(?:BUG|bug|issue) ?#?(\d+)',
-            'issue_server_link_bug': 'http://bug/{repo}/bug/\\1',
-            'issue_sub_bug': 'bug#\\1',
-            'issue_pat_empty_prefix': 'FAIL(\d+)',
-            'issue_server_link_empty_prefix': 'http://fail/{repo}/\\1',
-            'issue_sub_empty_prefix': '',
-            'issue_pat_absent_prefix': 'FAILMORE(\d+)',
-            'issue_server_link_absent_prefix': 'http://failmore/{repo}/\\1',
+            'sqlalchemy.url': r'foo',
+            'issue_pat': r'X(\d+)',
+            'issue_server_link': r'http://main/{repo}/main/\1/',
+            'issue_sub': r'#\1',
+            'issue_pat_pr': r'(?:pullrequest|pull request|PR|pr) ?#?(\d+)',
+            'issue_server_link_pr': r'http://pr/{repo}/pr/\1',
+            'issue_sub_pr': r'PR#\1',
+            'issue_pat_bug': r'(?:BUG|bug|issue) ?#?(\d+)',
+            'issue_server_link_bug': r'http://bug/{repo}/bug/\1',
+            'issue_sub_bug': r'bug#\1',
+            'issue_pat_empty_prefix': r'FAIL(\d+)',
+            'issue_server_link_empty_prefix': r'http://fail/{repo}/\1',
+            'issue_sub_empty_prefix': r'',
+            'issue_pat_absent_prefix': r'FAILMORE(\d+)',
+            'issue_server_link_absent_prefix': r'http://failmore/{repo}/\1',
         }
         # force recreation of lazy function
         with mock.patch('kallithea.lib.helpers._urlify_issues_f', None):
             with mock.patch('kallithea.CONFIG', config_stub):
                 assert urlify_text(sample, 'repo_name') == expected
 
-    @parametrize('test,expected', [
+    @base.parametrize('test,expected', [
       ("", None),
       ("/_2", None),
       ("_2", 2),
@@ -542,9 +542,9 @@
     def test_get_permanent_id(self, test, expected):
         from kallithea.lib.utils import _get_permanent_id
         extracted = _get_permanent_id(test)
-        assert extracted == expected, 'url:%s, got:`%s` expected: `%s`' % (test, _test, expected)
+        assert extracted == expected, 'url:%s, got:`%s` expected: `%s`' % (test, base._test, expected)
 
-    @parametrize('test,expected', [
+    @base.parametrize('test,expected', [
       ("", ""),
       ("/", "/"),
       ("/_ID", '/_ID'),
@@ -555,14 +555,14 @@
       ("_IDa", '_IDa'),
     ])
     def test_fix_repo_id_name(self, test, expected):
-        repo = Repository.get_by_repo_name(HG_REPO)
+        repo = Repository.get_by_repo_name(base.HG_REPO)
         test = test.replace('ID', str(repo.repo_id))
         expected = expected.replace('NAME', repo.repo_name).replace('ID', str(repo.repo_id))
         from kallithea.lib.utils import fix_repo_id_name
         replaced = fix_repo_id_name(test)
         assert replaced == expected, 'url:%s, got:`%s` expected: `%s`' % (test, replaced, expected)
 
-    @parametrize('canonical,test,expected', [
+    @base.parametrize('canonical,test,expected', [
         ('http://www.example.org/', '/abc/xyz', 'http://www.example.org/abc/xyz'),
         ('http://www.example.org', '/abc/xyz', 'http://www.example.org/abc/xyz'),
         ('http://www.example.org', '/abc/xyz/', 'http://www.example.org/abc/xyz/'),
@@ -590,7 +590,7 @@
             with mock.patch('kallithea.CONFIG', config_mock):
                 assert canonical_url(test) == expected
 
-    @parametrize('canonical,expected', [
+    @base.parametrize('canonical,expected', [
         ('http://www.example.org', 'www.example.org'),
         ('http://www.example.org/repos/', 'www.example.org'),
         ('http://www.example.org/kallithea/repos/', 'www.example.org'),
--- a/kallithea/tests/other/test_mail.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/other/test_mail.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,8 +1,10 @@
+# -*- coding: utf-8 -*-
+
 import mock
 
 import kallithea
 from kallithea.model.db import User
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
 class smtplib_mock(object):
@@ -25,7 +27,7 @@
 
 
 @mock.patch('kallithea.lib.rcmail.smtp_mailer.smtplib', smtplib_mock)
-class TestMail(TestController):
+class TestMail(base.TestController):
 
     def test_send_mail_trivial(self):
         mailserver = 'smtp.mailserver.org'
@@ -66,7 +68,7 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 
-        assert smtplib_mock.lastdest == set([TEST_USER_ADMIN_EMAIL, email_to])
+        assert smtplib_mock.lastdest == set([base.TEST_USER_ADMIN_EMAIL, email_to])
         assert smtplib_mock.lastsender == envelope_from
         assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
         assert 'Subject: %s' % subject in smtplib_mock.lastmsg
@@ -90,7 +92,7 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 
-        assert smtplib_mock.lastdest == set([TEST_USER_ADMIN_EMAIL] + email_to.split(','))
+        assert smtplib_mock.lastdest == set([base.TEST_USER_ADMIN_EMAIL] + email_to.split(','))
         assert smtplib_mock.lastsender == envelope_from
         assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
         assert 'Subject: %s' % subject in smtplib_mock.lastmsg
@@ -112,7 +114,7 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 
-        assert smtplib_mock.lastdest == set([TEST_USER_ADMIN_EMAIL])
+        assert smtplib_mock.lastdest == set([base.TEST_USER_ADMIN_EMAIL])
         assert smtplib_mock.lastsender == envelope_from
         assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
         assert 'Subject: %s' % subject in smtplib_mock.lastmsg
@@ -126,7 +128,7 @@
         subject = 'subject'
         body = 'body'
         html_body = 'html_body'
-        author = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        author = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
 
         config_mock = {
             'smtp_server': mailserver,
@@ -144,13 +146,13 @@
 
     def test_send_mail_with_author_full_mail_from(self):
         mailserver = 'smtp.mailserver.org'
-        recipients = ['rcpt1', 'rcpt2']
+        recipients = ['ræcpt1', 'receptor2 <rcpt2@example.com>', 'tæst@example.com', 'Tæst <test@example.com>']
         envelope_addr = 'noreply@mailserver.org'
-        envelope_from = 'Some Name <%s>' % envelope_addr
+        envelope_from = 'Söme Næme <%s>' % envelope_addr
         subject = 'subject'
         body = 'body'
         html_body = 'html_body'
-        author = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        author = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
 
         config_mock = {
             'smtp_server': mailserver,
--- a/kallithea/tests/other/test_validators.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/other/test_validators.py	Thu Feb 06 01:19:23 2020 +0100
@@ -6,7 +6,7 @@
 from kallithea.model.meta import Session
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.user_group import UserGroupModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -14,7 +14,7 @@
 
 
 @pytest.mark.usefixtures("test_context_fixture") # apply fixture for all test methods
-class TestRepoGroups(TestController):
+class TestRepoGroups(base.TestController):
 
     def teardown_method(self, method):
         Session.remove()
@@ -40,7 +40,7 @@
         with pytest.raises(formencode.Invalid):
             validator.to_python('.,')
         with pytest.raises(formencode.Invalid):
-            validator.to_python(TEST_USER_ADMIN_LOGIN)
+            validator.to_python(base.TEST_USER_ADMIN_LOGIN)
         assert 'test' == validator.to_python('test')
 
         validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
@@ -49,7 +49,7 @@
         validator = v.ValidRepoUser()
         with pytest.raises(formencode.Invalid):
             validator.to_python('nouser')
-        assert TEST_USER_ADMIN_LOGIN == validator.to_python(TEST_USER_ADMIN_LOGIN)
+        assert base.TEST_USER_ADMIN_LOGIN == validator.to_python(base.TEST_USER_ADMIN_LOGIN)
 
     def test_ValidUserGroup(self):
         validator = v.ValidUserGroup()
@@ -82,11 +82,11 @@
         validator = v.ValidRepoGroup()
         model = RepoGroupModel()
         with pytest.raises(formencode.Invalid):
-            validator.to_python({'group_name': HG_REPO, })
+            validator.to_python({'group_name': base.HG_REPO, })
         gr = model.create(group_name=u'test_gr', group_description=u'desc',
                           parent=None,
                           just_db=True,
-                          owner=TEST_USER_ADMIN_LOGIN)
+                          owner=base.TEST_USER_ADMIN_LOGIN)
         with pytest.raises(formencode.Invalid):
             validator.to_python({'group_name': gr.group_name, })
 
@@ -127,8 +127,8 @@
     def test_ValidAuth(self):
         validator = v.ValidAuth()
         valid_creds = {
-            'username': TEST_USER_REGULAR2_LOGIN,
-            'password': TEST_USER_REGULAR2_PASS,
+            'username': base.TEST_USER_REGULAR2_LOGIN,
+            'password': base.TEST_USER_REGULAR2_PASS,
         }
         invalid_creds = {
             'username': 'err',
@@ -145,12 +145,12 @@
             validator.to_python({'repo_name': ''})
 
         with pytest.raises(formencode.Invalid):
-            validator.to_python({'repo_name': HG_REPO})
+            validator.to_python({'repo_name': base.HG_REPO})
 
         gr = RepoGroupModel().create(group_name=u'group_test',
                                       group_description=u'desc',
                                       parent=None,
-                                      owner=TEST_USER_ADMIN_LOGIN)
+                                      owner=base.TEST_USER_ADMIN_LOGIN)
         with pytest.raises(formencode.Invalid):
             validator.to_python({'repo_name': gr.group_name})
 
@@ -163,7 +163,7 @@
         # this uses ValidRepoName validator
         assert True
 
-    @parametrize('name,expected', [
+    @base.parametrize('name,expected', [
         ('test', 'test'), ('lolz!', 'lolz'), ('  aavv', 'aavv'),
         ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
         ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
@@ -196,7 +196,7 @@
 
     def test_ValidPath(self):
             validator = v.ValidPath()
-            assert TESTS_TMP_PATH == validator.to_python(TESTS_TMP_PATH)
+            assert base.TESTS_TMP_PATH == validator.to_python(base.TESTS_TMP_PATH)
             with pytest.raises(formencode.Invalid):
                 validator.to_python('/no_such_dir')
 
@@ -205,20 +205,20 @@
 
         assert 'mail@python.org' == validator.to_python('MaiL@Python.org')
 
-        email = TEST_USER_REGULAR2_EMAIL
+        email = base.TEST_USER_REGULAR2_EMAIL
         with pytest.raises(formencode.Invalid):
             validator.to_python(email)
 
     def test_ValidSystemEmail(self):
         validator = v.ValidSystemEmail()
-        email = TEST_USER_REGULAR2_EMAIL
+        email = base.TEST_USER_REGULAR2_EMAIL
 
         assert email == validator.to_python(email)
         with pytest.raises(formencode.Invalid):
             validator.to_python('err')
 
     def test_LdapLibValidator(self):
-        if ldap_lib_installed:
+        if base.ldap_lib_installed:
             validator = v.LdapLibValidator()
             assert "DN" == validator.to_python('DN')
         else:
--- a/kallithea/tests/other/test_vcs_operations.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/other/test_vcs_operations.py	Thu Feb 06 01:19:23 2020 +0100
@@ -32,18 +32,19 @@
 import re
 import tempfile
 import time
-import urllib2
+import urllib.request
 from subprocess import PIPE, Popen
 from tempfile import _RandomNameSequence
 
 import pytest
 
 from kallithea import CONFIG
+from kallithea.lib.utils2 import ascii_bytes, safe_str
 from kallithea.model.db import CacheInvalidation, Repository, Ui, User, UserIpMap, UserLog
 from kallithea.model.meta import Session
 from kallithea.model.ssh_key import SshKeyModel
 from kallithea.model.user import UserModel
-from kallithea.tests.base import *
+from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
 
 
@@ -64,12 +65,12 @@
 
 class SshVcsTest(object):
     public_keys = {
-        TEST_USER_REGULAR_LOGIN: u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== kallithea@localhost',
-        TEST_USER_ADMIN_LOGIN: u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUq== kallithea@localhost',
+        base.TEST_USER_REGULAR_LOGIN: u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== kallithea@localhost',
+        base.TEST_USER_ADMIN_LOGIN: u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUq== kallithea@localhost',
     }
 
     @classmethod
-    def repo_url_param(cls, webserver, repo_name, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS, client_ip=IP_ADDR):
+    def repo_url_param(cls, webserver, repo_name, username=base.TEST_USER_ADMIN_LOGIN, password=base.TEST_USER_ADMIN_PASS, client_ip=base.IP_ADDR):
         user = User.get_by_username(username)
         if user.ssh_keys:
             ssh_key = user.ssh_keys[0]
@@ -83,11 +84,11 @@
 # Mixins for using Mercurial and Git
 class HgVcsTest(object):
     repo_type = 'hg'
-    repo_name = HG_REPO
+    repo_name = base.HG_REPO
 
 class GitVcsTest(object):
     repo_type = 'git'
-    repo_name = GIT_REPO
+    repo_name = base.GIT_REPO
 
 # Combine mixins to give the combinations we want to parameterize tests with
 class HgHttpVcsTest(HgVcsTest, HttpVcsTest):
@@ -118,17 +119,17 @@
             ssh_key.user_ssh_key_id)
         return "ssh://someuser@somehost/%s""" % repo_name
 
-parametrize_vcs_test = parametrize('vt', [
+parametrize_vcs_test = base.parametrize('vt', [
     HgHttpVcsTest,
     GitHttpVcsTest,
     HgSshVcsTest,
     GitSshVcsTest,
 ])
-parametrize_vcs_test_hg = parametrize('vt', [
+parametrize_vcs_test_hg = base.parametrize('vt', [
     HgHttpVcsTest,
     HgSshVcsTest,
 ])
-parametrize_vcs_test_http = parametrize('vt', [
+parametrize_vcs_test_http = base.parametrize('vt', [
     HgHttpVcsTest,
     GitHttpVcsTest,
 ])
@@ -162,11 +163,11 @@
                 print('stderr:', stderr)
         if not ignoreReturnCode:
             assert p.returncode == 0
-        return stdout, stderr
+        return safe_str(stdout), safe_str(stderr)
 
 
 def _get_tmp_dir(prefix='vcs_operations-', suffix=''):
-    return tempfile.mkdtemp(dir=TESTS_TMP_PATH, prefix=prefix, suffix=suffix)
+    return tempfile.mkdtemp(dir=base.TESTS_TMP_PATH, prefix=prefix, suffix=suffix)
 
 
 def _add_files(vcs, dest_dir, files_no=3):
@@ -177,7 +178,7 @@
     :param vcs:
     :param dest_dir:
     """
-    added_file = '%ssetup.py' % _RandomNameSequence().next()
+    added_file = '%ssetup.py' % next(_RandomNameSequence())
     open(os.path.join(dest_dir, added_file), 'a').close()
     Command(dest_dir).execute(vcs, 'add', added_file)
 
@@ -186,7 +187,7 @@
         author_str = 'User <%s>' % email
     else:
         author_str = 'User ǝɯɐᴎ <%s>' % email
-    for i in xrange(files_no):
+    for i in range(files_no):
         cmd = """echo "added_line%s" >> %s""" % (i, added_file)
         Command(dest_dir).execute(cmd)
         if vcs == 'hg':
@@ -242,7 +243,7 @@
 
 
 @pytest.mark.usefixtures("test_context_fixture")
-class TestVCSOperations(TestController):
+class TestVCSOperations(base.TestController):
 
     @classmethod
     def setup_class(cls):
@@ -262,16 +263,16 @@
     @pytest.fixture(scope="module")
     def testfork(self):
         # create fork so the repo stays untouched
-        git_fork_name = u'%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
-        fixture.create_fork(GIT_REPO, git_fork_name)
-        hg_fork_name = u'%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
-        fixture.create_fork(HG_REPO, hg_fork_name)
+        git_fork_name = u'%s_fork%s' % (base.GIT_REPO, next(_RandomNameSequence()))
+        fixture.create_fork(base.GIT_REPO, git_fork_name)
+        hg_fork_name = u'%s_fork%s' % (base.HG_REPO, next(_RandomNameSequence()))
+        fixture.create_fork(base.HG_REPO, hg_fork_name)
         return {'git': git_fork_name, 'hg': hg_fork_name}
 
     @parametrize_vcs_test
     def test_clone_repo_by_admin(self, webserver, vt):
         clone_url = vt.repo_url_param(webserver, vt.repo_name)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir())
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir())
 
         if vt.repo_type == 'git':
             assert 'Cloning into' in stdout + stderr
@@ -286,26 +287,26 @@
     @parametrize_vcs_test_http
     def test_clone_wrong_credentials(self, webserver, vt):
         clone_url = vt.repo_url_param(webserver, vt.repo_name, password='bad!')
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
         if vt.repo_type == 'git':
             assert 'fatal: Authentication failed' in stderr
         elif vt.repo_type == 'hg':
             assert 'abort: authorization failed' in stderr
 
     def test_clone_git_dir_as_hg(self, webserver):
-        clone_url = HgHttpVcsTest.repo_url_param(webserver, GIT_REPO)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        clone_url = HgHttpVcsTest.repo_url_param(webserver, base.GIT_REPO)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
         assert 'HTTP Error 404: Not Found' in stderr or "not a valid repository" in stdout and 'abort:' in stderr
 
     def test_clone_hg_repo_as_git(self, webserver):
-        clone_url = GitHttpVcsTest.repo_url_param(webserver, HG_REPO)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        clone_url = GitHttpVcsTest.repo_url_param(webserver, base.HG_REPO)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
         assert 'not found' in stderr
 
     @parametrize_vcs_test
     def test_clone_non_existing_path(self, webserver, vt):
         clone_url = vt.repo_url_param(webserver, 'trololo')
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
         if vt.repo_type == 'git':
             assert 'not found' in stderr or 'abort: Access to %r denied' % 'trololo' in stderr
         elif vt.repo_type == 'hg':
@@ -318,21 +319,21 @@
         Session().commit()
 
         # Create an empty server repo using the API
-        repo_name = u'new_%s_%s' % (vt.repo_type, _RandomNameSequence().next())
-        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        repo_name = u'new_%s_%s' % (vt.repo_type, next(_RandomNameSequence()))
+        usr = User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
         params = {
             "id": 7,
             "api_key": usr.api_key,
             "method": 'create_repo',
             "args": dict(repo_name=repo_name,
-                         owner=TEST_USER_ADMIN_LOGIN,
+                         owner=base.TEST_USER_ADMIN_LOGIN,
                          repo_type=vt.repo_type),
         }
-        req = urllib2.Request(
+        req = urllib.request.Request(
             'http://%s:%s/_admin/api' % webserver.server_address,
-            data=json.dumps(params),
+            data=ascii_bytes(json.dumps(params)),
             headers={'content-type': 'application/json'})
-        response = urllib2.urlopen(req)
+        response = urllib.request.urlopen(req)
         result = json.loads(response.read())
         # Expect something like:
         # {u'result': {u'msg': u'Created new repository `new_XXX`', u'task': None, u'success': True}, u'id': 7, u'error': None}
@@ -341,7 +342,7 @@
         # Create local clone of the empty server repo
         local_clone_dir = _get_tmp_dir()
         clone_url = vt.repo_url_param(webserver, repo_name)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, local_clone_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, local_clone_dir)
 
         # Make 3 commits and push to the empty server repo.
         # The server repo doesn't have any other heads than the
@@ -378,7 +379,7 @@
 
         dest_dir = _get_tmp_dir()
         clone_url = vt.repo_url_param(webserver, vt.repo_name)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
 
         clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type])
         stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, clone_url=clone_url)
@@ -400,7 +401,7 @@
         Session().commit()
 
         dest_dir = _get_tmp_dir()
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'init', dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'init', dest_dir)
 
         clone_url = vt.repo_url_param(webserver, vt.repo_name)
         stdout, stderr = Command(dest_dir).execute(vt.repo_type, 'pull', clone_url)
@@ -413,6 +414,32 @@
         action_parts = [ul.action for ul in UserLog.query().order_by(UserLog.user_log_id)]
         assert action_parts == [u'pull']
 
+        # Test handling of URLs with extra '/' around repo_name
+        stdout, stderr = Command(dest_dir).execute(vt.repo_type, 'pull', clone_url.replace('/' + vt.repo_name, '/./%s/' % vt.repo_name), ignoreReturnCode=True)
+        if issubclass(vt, HttpVcsTest):
+            if vt.repo_type == 'git':
+                # NOTE: when pulling from http://hostname/./vcs_test_git/ , the git client will normalize that and issue an HTTP request to /vcs_test_git/info/refs
+                assert 'Already up to date.' in stdout
+            else:
+                assert vt.repo_type == 'hg'
+                assert "abort: HTTP Error 404: Not Found" in stderr
+        else:
+            assert issubclass(vt, SshVcsTest)
+            if vt.repo_type == 'git':
+                assert "abort: Access to './%s' denied" % vt.repo_name in stderr
+            else:
+                assert "abort: Access to './%s' denied" % vt.repo_name in stdout
+
+        stdout, stderr = Command(dest_dir).execute(vt.repo_type, 'pull', clone_url.replace('/' + vt.repo_name, '/%s/' % vt.repo_name), ignoreReturnCode=True)
+        if vt.repo_type == 'git':
+            assert 'Already up to date.' in stdout
+        else:
+            assert vt.repo_type == 'hg'
+            assert "no changes found" in stdout
+        assert "denied" not in stderr
+        assert "denied" not in stdout
+        assert "404" not in stdout
+
     @parametrize_vcs_test
     def test_push_invalidates_cache(self, webserver, testfork, vt):
         pre_cached_tip = [repo.get_api_data()['last_changeset']['short_id'] for repo in Repository.query().filter(Repository.repo_name == testfork[vt.repo_type])]
@@ -428,7 +455,7 @@
 
         dest_dir = _get_tmp_dir()
         clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type])
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
 
         stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, files_no=1, clone_url=clone_url)
 
@@ -446,7 +473,7 @@
     def test_push_wrong_credentials(self, webserver, vt):
         dest_dir = _get_tmp_dir()
         clone_url = vt.repo_url_param(webserver, vt.repo_name)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
 
         clone_url = webserver.repo_url(vt.repo_name, username='bad', password='name')
         stdout, stderr = _add_files_and_push(webserver, vt, dest_dir,
@@ -463,8 +490,8 @@
         Session().commit()
 
         dest_dir = _get_tmp_dir()
-        clone_url = vt.repo_url_param(webserver, vt.repo_name, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
+        clone_url = vt.repo_url_param(webserver, vt.repo_name, username=base.TEST_USER_REGULAR_LOGIN, password=base.TEST_USER_REGULAR_PASS)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
 
         stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, ignoreReturnCode=True, clone_url=clone_url)
 
@@ -481,7 +508,7 @@
     def test_push_back_to_wrong_url(self, webserver, vt):
         dest_dir = _get_tmp_dir()
         clone_url = vt.repo_url_param(webserver, vt.repo_name)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
 
         stdout, stderr = _add_files_and_push(
             webserver, vt, dest_dir, clone_url='http://%s:%s/tmp' % (
@@ -498,12 +525,12 @@
         user_model = UserModel()
         try:
             # Add IP constraint that excludes the test context:
-            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            user_model.add_extra_ip(base.TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
             Session().commit()
             # IP permissions are cached, need to wait for the cache in the server process to expire
             time.sleep(1.5)
             clone_url = vt.repo_url_param(webserver, vt.repo_name)
-            stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+            stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
             if vt.repo_type == 'git':
                 # The message apparently changed in Git 1.8.3, so match it loosely.
                 assert re.search(r'\b403\b', stderr) or 'abort: User test_admin from 127.0.0.127 cannot be authorized' in stderr
@@ -518,7 +545,7 @@
             time.sleep(1.5)
 
         clone_url = vt.repo_url_param(webserver, vt.repo_name)
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir())
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir())
 
         if vt.repo_type == 'git':
             assert 'Cloning into' in stdout + stderr
@@ -537,9 +564,9 @@
         Ui.create_or_update_hook('preoutgoing.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
         Session().commit()
         # clone repo
-        clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
+        clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type], username=base.TEST_USER_ADMIN_LOGIN, password=base.TEST_USER_ADMIN_PASS)
         dest_dir = _get_tmp_dir()
-        stdout, stderr = Command(TESTS_TMP_PATH) \
+        stdout, stderr = Command(base.TESTS_TMP_PATH) \
             .execute(vt.repo_type, 'clone', clone_url, dest_dir, ignoreReturnCode=True)
         if vt.repo_type == 'hg':
             assert 'preoutgoing.testhook hook failed' in stdout
@@ -552,9 +579,9 @@
         Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
         Session().commit()
         # clone repo
-        clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
+        clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type], username=base.TEST_USER_ADMIN_LOGIN, password=base.TEST_USER_ADMIN_PASS)
         dest_dir = _get_tmp_dir()
-        stdout, stderr = Command(TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
 
         stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, clone_url,
                                              ignoreReturnCode=True)
@@ -595,19 +622,19 @@
 
     def test_add_submodule_git(self, webserver, testfork):
         dest_dir = _get_tmp_dir()
-        clone_url = GitHttpVcsTest.repo_url_param(webserver, GIT_REPO)
+        clone_url = GitHttpVcsTest.repo_url_param(webserver, base.GIT_REPO)
 
         fork_url = GitHttpVcsTest.repo_url_param(webserver, testfork['git'])
 
         # add submodule
-        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', fork_url, dest_dir)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute('git clone', fork_url, dest_dir)
         stdout, stderr = Command(dest_dir).execute('git submodule add', clone_url, 'testsubmodule')
-        stdout, stderr = Command(dest_dir).execute('git commit -am "added testsubmodule pointing to', clone_url, '"', EMAIL=TEST_USER_ADMIN_EMAIL)
+        stdout, stderr = Command(dest_dir).execute('git commit -am "added testsubmodule pointing to', clone_url, '"', EMAIL=base.TEST_USER_ADMIN_EMAIL)
         stdout, stderr = Command(dest_dir).execute('git push', fork_url, 'master')
 
         # check for testsubmodule link in files page
         self.log_user()
-        response = self.app.get(url(controller='files', action='index',
+        response = self.app.get(base.url(controller='files', action='index',
                                     repo_name=testfork['git'],
                                     revision='tip',
                                     f_path='/'))
@@ -617,7 +644,7 @@
         response.mustcontain('<a class="submodule-dir" href="%s" target="_blank"><i class="icon-file-submodule"></i><span>testsubmodule @ ' % clone_url)
 
         # check that following a submodule link actually works - and redirects
-        response = self.app.get(url(controller='files', action='index',
+        response = self.app.get(base.url(controller='files', action='index',
                                     repo_name=testfork['git'],
                                     revision='tip',
                                     f_path='/testsubmodule'),
--- a/kallithea/tests/performance/test_vcs.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/performance/test_vcs.py	Thu Feb 06 01:19:23 2020 +0100
@@ -15,11 +15,11 @@
 import pytest
 
 from kallithea.model.db import Repository
-from kallithea.tests.base import *
+from kallithea.tests import base
 
 
-@pytest.mark.skipif("not os.environ.has_key('TEST_PERFORMANCE')", reason="skipping performance tests, set TEST_PERFORMANCE in environment if desired")
-class TestVCSPerformance(TestController):
+@pytest.mark.skipif("'TEST_PERFORMANCE' not in os.environ", reason="skipping performance tests, set TEST_PERFORMANCE in environment if desired")
+class TestVCSPerformance(base.TestController):
 
     def graphmod(self, repo):
         """ Simple test for running the graph_data function for profiling/testing performance. """
@@ -31,7 +31,7 @@
         jsdata = graph_data(scm_inst, revs)
 
     def test_graphmod_hg(self, benchmark):
-        benchmark(self.graphmod, HG_REPO)
+        benchmark(self.graphmod, base.HG_REPO)
 
     def test_graphmod_git(self, benchmark):
-        benchmark(self.graphmod, GIT_REPO)
+        benchmark(self.graphmod, base.GIT_REPO)
--- a/kallithea/tests/scripts/manual_test_concurrency.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/scripts/manual_test_concurrency.py	Thu Feb 06 01:19:23 2020 +0100
@@ -41,7 +41,6 @@
 
 from kallithea.config.environment import load_environment
 from kallithea.lib.auth import get_crypt_password
-from kallithea.lib.utils import setup_cache_regions
 from kallithea.model import meta
 from kallithea.model.base import init_model
 from kallithea.model.db import Repository, Ui, User
@@ -52,8 +51,6 @@
 conf = appconfig('config:development.ini', relative_to=rel_path)
 load_environment(conf.global_conf, conf.local_conf)
 
-setup_cache_regions(conf)
-
 USER = TEST_USER_ADMIN_LOGIN
 PASS = TEST_USER_ADMIN_PASS
 HOST = 'server.local'
@@ -205,7 +202,7 @@
             backend = 'hg'
 
         if METHOD == 'pull':
-            seq = tempfile._RandomNameSequence().next()
+            seq = next(tempfile._RandomNameSequence())
             test_clone_with_credentials(repo=sys.argv[1], method='clone',
                                         backend=backend)
         s = time.time()
--- a/kallithea/tests/scripts/manual_test_crawler.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/scripts/manual_test_crawler.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- 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
@@ -32,13 +32,13 @@
 
 from __future__ import print_function
 
-import cookielib
+import http.cookiejar
 import os
 import sys
 import tempfile
 import time
-import urllib
-import urllib2
+import urllib.parse
+import urllib.request
 from os.path import dirname
 
 from kallithea.lib import vcs
@@ -72,18 +72,18 @@
 ]
 
 
-cj = cookielib.FileCookieJar(os.path.join(tempfile.gettempdir(), 'rc_test_cookie.txt'))
-o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
+cj = http.cookiejar.FileCookieJar(os.path.join(tempfile.gettempdir(), 'rc_test_cookie.txt'))
+o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
 o.addheaders = [
     ('User-agent', 'kallithea-crawler'),
     ('Accept-Language', 'en - us, en;q = 0.5')
 ]
 
-urllib2.install_opener(o)
+urllib.request.install_opener(o)
 
 
 def _get_repo(proj):
-    if isinstance(proj, basestring):
+    if isinstance(proj, str):
         repo = vcs.get_repo(os.path.join(PROJECT_PATH, proj))
         proj = proj
     else:
@@ -101,7 +101,7 @@
 
         page = '/'.join((proj, 'changelog',))
 
-        full_uri = (BASE_URI % page) + '?' + urllib.urlencode({'page': i})
+        full_uri = (BASE_URI % page) + '?' + urllib.parse.urlencode({'page': i})
         s = time.time()
         f = o.open(full_uri)
 
@@ -130,13 +130,13 @@
             break
 
         full_uri = (BASE_URI % raw_cs)
-        print('%s visiting %s\%s' % (cnt, full_uri, i))
+        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('%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))
--- a/kallithea/tests/vcs/base.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/base.py	Thu Feb 06 01:19:23 2020 +0100
@@ -79,8 +79,8 @@
             for node in commit.get('removed', []):
                 cls.imc.remove(FileNode(node.path))
 
-            cls.tip = cls.imc.commit(message=unicode(commit['message']),
-                                     author=unicode(commit['author']),
+            cls.tip = cls.imc.commit(message=commit['message'],
+                                     author=commit['author'],
                                      date=commit['date'])
 
     @pytest.fixture(autouse=True)
--- a/kallithea/tests/vcs/test_archives.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_archives.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,6 +1,6 @@
 import datetime
+import io
 import os
-import StringIO
 import tarfile
 import tempfile
 import zipfile
@@ -18,7 +18,7 @@
     @classmethod
     def _get_commits(cls):
         start_date = datetime.datetime(2010, 1, 1, 20)
-        for x in xrange(5):
+        for x in range(5):
             yield {
                 'message': 'Commit %d' % x,
                 'author': 'Joe Doe <joe.doe@example.com>',
@@ -35,11 +35,10 @@
             self.tip.fill_archive(stream=f, kind='zip', prefix='repo')
         out = zipfile.ZipFile(path)
 
-        for x in xrange(5):
+        for x in range(5):
             node_path = '%d/file_%d.txt' % (x, x)
-            decompressed = StringIO.StringIO()
-            decompressed.write(out.read('repo/' + node_path))
-            assert decompressed.getvalue() == self.tip.get_node(node_path).content
+            decompressed = out.read('repo/' + node_path)
+            assert decompressed == self.tip.get_node(node_path).content
 
     def test_archive_tgz(self):
         path = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_tgz-')[1]
@@ -50,9 +49,9 @@
         outfile = tarfile.open(path, 'r|gz')
         outfile.extractall(outdir)
 
-        for x in xrange(5):
+        for x in range(5):
             node_path = '%d/file_%d.txt' % (x, x)
-            assert open(os.path.join(outdir, 'repo/' + node_path)).read() == self.tip.get_node(node_path).content
+            assert open(os.path.join(outdir, 'repo/' + node_path), 'rb').read() == self.tip.get_node(node_path).content
 
     def test_archive_tbz2(self):
         path = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_tbz2-')[1]
@@ -63,15 +62,15 @@
         outfile = tarfile.open(path, 'r|bz2')
         outfile.extractall(outdir)
 
-        for x in xrange(5):
+        for x in range(5):
             node_path = '%d/file_%d.txt' % (x, x)
-            assert open(os.path.join(outdir, 'repo/' + node_path)).read() == self.tip.get_node(node_path).content
+            assert open(os.path.join(outdir, 'repo/' + node_path), 'rb').read() == self.tip.get_node(node_path).content
 
     def test_archive_default_stream(self):
         tmppath = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_default_stream-')[1]
         with open(tmppath, 'wb') as stream:
             self.tip.fill_archive(stream=stream)
-        mystream = StringIO.StringIO()
+        mystream = io.BytesIO()
         self.tip.fill_archive(stream=mystream)
         mystream.seek(0)
         with open(tmppath, 'rb') as f:
--- a/kallithea/tests/vcs/test_changesets.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_changesets.py	Thu Feb 06 01:19:23 2020 +0100
@@ -15,7 +15,6 @@
 
     def test_as_dict(self):
         changeset = BaseChangeset()
-        changeset.id = 'ID'
         changeset.raw_id = 'RAW_ID'
         changeset.short_id = 'SHORT_ID'
         changeset.revision = 1009
@@ -26,7 +25,6 @@
         changeset.changed = []
         changeset.removed = []
         assert changeset.as_dict() == {
-            'id': 'ID',
             'raw_id': 'RAW_ID',
             'short_id': 'SHORT_ID',
             'revision': 1009,
@@ -47,7 +45,7 @@
     @classmethod
     def _get_commits(cls):
         start_date = datetime.datetime(2010, 1, 1, 20)
-        for x in xrange(5):
+        for x in range(5):
             yield {
                 'message': 'Commit %d' % x,
                 'author': 'Joe Doe <joe.doe@example.com>',
@@ -69,7 +67,7 @@
         assert foobar_tip.branch == 'foobar'
         assert foobar_tip.branches == ['foobar']
         # 'foobar' should be the only branch that contains the new commit
-        branch_tips = self.repo.branches.values()
+        branch_tips = list(self.repo.branches.values())
         assert branch_tips.count(str(foobar_tip.raw_id)) == 1
 
     def test_new_head_in_default_branch(self):
@@ -121,11 +119,11 @@
         assert doc_changeset not in default_branch_changesets
 
     def test_get_changeset_by_branch(self):
-        for branch, sha in self.repo.branches.iteritems():
+        for branch, sha in self.repo.branches.items():
             assert sha == self.repo.get_changeset(branch).raw_id
 
     def test_get_changeset_by_tag(self):
-        for tag, sha in self.repo.tags.iteritems():
+        for tag, sha in self.repo.tags.items():
             assert sha == self.repo.get_changeset(tag).raw_id
 
     def test_get_changeset_parents(self):
@@ -145,7 +143,7 @@
     @classmethod
     def _get_commits(cls):
         start_date = datetime.datetime(2010, 1, 1, 20)
-        for x in xrange(5):
+        for x in range(5):
             yield {
                 'message': u'Commit %d' % x,
                 'author': u'Joe Doe <joe.doe@example.com>',
@@ -240,7 +238,7 @@
     def test_get_filenodes_generator(self):
         tip = self.repo.get_changeset()
         filepaths = [node.path for node in tip.get_filenodes_generator()]
-        assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
+        assert filepaths == ['file_%d.txt' % x for x in range(5)]
 
     def test_size(self):
         tip = self.repo.get_changeset()
--- a/kallithea/tests/vcs/test_filenodes_unicode_path.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_filenodes_unicode_path.py	Thu Feb 06 01:19:23 2020 +0100
@@ -8,29 +8,22 @@
 
 class FileNodeUnicodePathTestsMixin(_BackendTestMixin):
 
-    fname = 'ąśðąęłąć.txt'
-    ufname = (fname).decode('utf-8')
+    fname = u'ąśðąęłąć.txt'
 
     @classmethod
     def _get_commits(cls):
-        cls.nodes = [
-            FileNode(cls.fname, content='Foobar'),
-        ]
-
-        commits = [
+        return [
             {
                 'message': 'Initial commit',
                 'author': 'Joe Doe <joe.doe@example.com>',
                 'date': datetime.datetime(2010, 1, 1, 20),
-                'added': cls.nodes,
+                'added': [FileNode(cls.fname, content='Foobar')],
             },
         ]
-        return commits
 
     def test_filenode_path(self):
         node = self.tip.get_node(self.fname)
-        unode = self.tip.get_node(self.ufname)
-        assert node == unode
+        assert node.path == self.fname
 
 
 class TestGitFileNodeUnicodePath(FileNodeUnicodePathTestsMixin):
--- a/kallithea/tests/vcs/test_getitem.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_getitem.py	Thu Feb 06 01:19:23 2020 +0100
@@ -9,7 +9,7 @@
     @classmethod
     def _get_commits(cls):
         start_date = datetime.datetime(2010, 1, 1, 20)
-        for x in xrange(5):
+        for x in range(5):
             yield {
                 'message': 'Commit %d' % x,
                 'author': 'Joe Doe <joe.doe@example.com>',
@@ -23,7 +23,7 @@
         assert self.repo[-1] == self.repo.get_changeset()
 
     def test__getitem__returns_correct_items(self):
-        changesets = [self.repo[x] for x in xrange(len(self.repo.revisions))]
+        changesets = [self.repo[x] for x in range(len(self.repo.revisions))]
         assert changesets == list(self.repo.get_changesets())
 
 
--- a/kallithea/tests/vcs/test_getslice.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_getslice.py	Thu Feb 06 01:19:23 2020 +0100
@@ -9,7 +9,7 @@
     @classmethod
     def _get_commits(cls):
         start_date = datetime.datetime(2010, 1, 1, 20)
-        for x in xrange(5):
+        for x in range(5):
             yield {
                 'message': 'Commit %d' % x,
                 'author': 'Joe Doe <joe.doe@example.com>',
--- a/kallithea/tests/vcs/test_git.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_git.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,7 +1,6 @@
 import datetime
 import os
 import sys
-import urllib2
 
 import mock
 import pytest
@@ -31,8 +30,8 @@
             GitRepository(wrong_repo_path)
 
     def test_git_cmd_injection(self):
-        repo_inject_path = TEST_GIT_REPO + '; echo "Cake";'
-        with pytest.raises(urllib2.URLError):
+        repo_inject_path = 'file:/%s; echo "Cake";' % TEST_GIT_REPO
+        with pytest.raises(RepositoryError):
             # Should fail because URL will contain the parts after ; too
             GitRepository(get_new_dir('injection-repo'), src_url=repo_inject_path, update_after_clone=True, create=True)
 
@@ -229,7 +228,7 @@
     def test_changeset10(self):
 
         chset10 = self.repo.get_changeset(self.repo.revisions[9])
-        readme = """===
+        readme = b"""===
 VCS
 ===
 
@@ -343,7 +342,7 @@
         start = offset
         end = limit and offset + limit or None
         sliced = list(self.repo[start:end])
-        pytest.failUnlessEqual(result, sliced,
+        pytest.assertEqual(result, sliced,
             msg="Comparison failed for limit=%s, offset=%s"
             "(get_changeset returned: %s and sliced: %s"
             % (limit, offset, result, sliced))
@@ -588,19 +587,19 @@
             'vcs/nodes.py']
         assert set(changed) == set([f.path for f in chset.changed])
 
-    def test_commit_message_is_unicode(self):
+    def test_commit_message_is_str(self):
         for cs in self.repo:
-            assert type(cs.message) == unicode
+            assert isinstance(cs.message, str)
 
-    def test_changeset_author_is_unicode(self):
+    def test_changeset_author_is_str(self):
         for cs in self.repo:
-            assert type(cs.author) == unicode
+            assert isinstance(cs.author, str)
 
-    def test_repo_files_content_is_unicode(self):
+    def test_repo_files_content_is_bytes(self):
         changeset = self.repo.get_changeset()
         for node in changeset.get_node('/'):
             if node.is_file():
-                assert type(node.content) == unicode
+                assert isinstance(node.content, bytes)
 
     def test_wrong_path(self):
         # There is 'setup.py' in the root dir but not there:
@@ -620,30 +619,6 @@
         assert 'marcink none@none' == self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992').author_name
 
 
-class TestGitSpecific():
-
-    def test_error_is_raised_for_added_if_diff_name_status_is_wrong(self):
-        repo = mock.MagicMock()
-        changeset = GitChangeset(repo, 'foobar')
-        changeset._diff_name_status = 'foobar'
-        with pytest.raises(VCSError):
-            changeset.added
-
-    def test_error_is_raised_for_changed_if_diff_name_status_is_wrong(self):
-        repo = mock.MagicMock()
-        changeset = GitChangeset(repo, 'foobar')
-        changeset._diff_name_status = 'foobar'
-        with pytest.raises(VCSError):
-            changeset.added
-
-    def test_error_is_raised_for_removed_if_diff_name_status_is_wrong(self):
-        repo = mock.MagicMock()
-        changeset = GitChangeset(repo, 'foobar')
-        changeset._diff_name_status = 'foobar'
-        with pytest.raises(VCSError):
-            changeset.added
-
-
 class TestGitSpecificWithRepo(_BackendTestMixin):
     backend_alias = 'git'
 
@@ -657,7 +632,7 @@
                 'added': [
                     FileNode('foobar/static/js/admin/base.js', content='base'),
                     FileNode('foobar/static/admin', content='admin',
-                        mode=0120000), # this is a link
+                        mode=0o120000), # this is a link
                     FileNode('foo', content='foo'),
                 ],
             },
@@ -673,11 +648,11 @@
 
     def test_paths_slow_traversing(self):
         cs = self.repo.get_changeset()
-        assert cs.get_node('foobar').get_node('static').get_node('js').get_node('admin').get_node('base.js').content == 'base'
+        assert cs.get_node('foobar').get_node('static').get_node('js').get_node('admin').get_node('base.js').content == b'base'
 
     def test_paths_fast_traversing(self):
         cs = self.repo.get_changeset()
-        assert cs.get_node('foobar/static/js/admin/base.js').content == 'base'
+        assert cs.get_node('foobar/static/js/admin/base.js').content == b'base'
 
     def test_workdir_get_branch(self):
         self.repo.run_git_command(['checkout', '-b', 'production'])
@@ -689,65 +664,65 @@
         assert 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._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(0, 1)
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(0), self.repo._get_revision(1)])
+             self.repo._get_revision(0), self.repo._get_revision(1)], cwd=self.repo.path)
 
     def test_get_diff_runs_git_command_with_str_hashes(self):
-        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1)
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['show', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(1)])
+             self.repo._get_revision(1)], cwd=self.repo.path)
 
     def test_get_diff_runs_git_command_with_path_if_its_given(self):
-        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(0, 1, 'foo')
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'], cwd=self.repo.path)
 
     def test_get_diff_does_not_sanitize_valid_context(self):
         almost_overflowed_long_int = 2**31-1
 
-        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(0, 1, 'foo', context=almost_overflowed_long_int)
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['diff', '-U' + str(almost_overflowed_long_int), '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'], cwd=self.repo.path)
 
     def test_get_diff_sanitizes_overflowing_context(self):
         overflowed_long_int = 2**31
         sanitized_overflowed_long_int = overflowed_long_int-1
 
-        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(0, 1, 'foo', context=overflowed_long_int)
 
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['diff', '-U' + str(sanitized_overflowed_long_int), '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'], cwd=self.repo.path)
 
     def test_get_diff_does_not_sanitize_zero_context(self):
         zero_context = 0
 
-        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(0, 1, 'foo', context=zero_context)
 
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['diff', '-U' + str(zero_context), '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'], cwd=self.repo.path)
 
     def test_get_diff_sanitizes_negative_context(self):
         negative_context = -10
 
-        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo._run_git_command = mock.Mock(return_value=(b'', b''))
         self.repo.get_diff(0, 1, 'foo', context=negative_context)
 
-        self.repo.run_git_command.assert_called_once_with(
+        self.repo._run_git_command.assert_called_once_with(
             ['diff', '-U0', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
-             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'], cwd=self.repo.path)
 
 
 class TestGitRegression(_BackendTestMixin):
@@ -804,22 +779,24 @@
         self.repo = GitRepository(self.repo_directory, create=True)
 
         # Create a dictionary where keys are hook names, and values are paths to
-        # them. Deduplicates code in tests a bit.
-        self.hook_directory = self.repo.get_hook_location()
-        self.kallithea_hooks = dict((h, os.path.join(self.hook_directory, h)) for h in ("pre-receive", "post-receive"))
+        # them in the non-bare repo. Deduplicates code in tests a bit.
+        self.kallithea_hooks = {
+            "pre-receive": os.path.join(self.repo.path, '.git', 'hooks', "pre-receive"),
+            "post-receive": os.path.join(self.repo.path, '.git', 'hooks', "post-receive"),
+        }
 
     def test_hooks_created_if_missing(self):
         """
         Tests if hooks are installed in repository if they are missing.
         """
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             if os.path.exists(hook_path):
                 os.remove(hook_path)
 
         ScmModel().install_git_hooks(repo=self.repo)
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             assert os.path.exists(hook_path)
 
     def test_kallithea_hooks_updated(self):
@@ -827,13 +804,13 @@
         Tests if hooks are updated if they are Kallithea hooks already.
         """
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             with open(hook_path, "w") as f:
                 f.write("KALLITHEA_HOOK_VER=0.0.0\nJUST_BOGUS")
 
         ScmModel().install_git_hooks(repo=self.repo)
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             with open(hook_path) as f:
                 assert "JUST_BOGUS" not in f.read()
 
@@ -842,13 +819,13 @@
         Tests if hooks are left untouched if they are not Kallithea hooks.
         """
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             with open(hook_path, "w") as f:
                 f.write("#!/bin/bash\n#CUSTOM_HOOK")
 
         ScmModel().install_git_hooks(repo=self.repo)
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             with open(hook_path) as f:
                 assert "CUSTOM_HOOK" in f.read()
 
@@ -857,12 +834,12 @@
         Tests if hooks are forcefully updated even though they are custom hooks.
         """
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             with open(hook_path, "w") as f:
                 f.write("#!/bin/bash\n#CUSTOM_HOOK")
 
         ScmModel().install_git_hooks(repo=self.repo, force_create=True)
 
-        for hook, hook_path in self.kallithea_hooks.iteritems():
+        for hook, hook_path in self.kallithea_hooks.items():
             with open(hook_path) as f:
                 assert "KALLITHEA_HOOK_VER" in f.read()
--- a/kallithea/tests/vcs/test_hg.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_hg.py	Thu Feb 06 01:19:23 2020 +0100
@@ -3,7 +3,6 @@
 import mock
 import pytest
 
-from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs.backends.hg import MercurialChangeset, MercurialRepository
 from kallithea.lib.vcs.exceptions import NodeDoesNotExistError, RepositoryError, VCSError
 from kallithea.lib.vcs.nodes import NodeKind, NodeState
@@ -19,7 +18,7 @@
                       % TEST_HG_REPO_CLONE)
 
     def setup_method(self):
-        self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
+        self.repo = MercurialRepository(TEST_HG_REPO)
 
     def test_wrong_repo_path(self):
         wrong_repo_path = os.path.join(TESTS_TMP_PATH, 'errorrepo')
@@ -32,7 +31,7 @@
 
     def test_repo_clone(self):
         self.__check_for_existing_repo()
-        repo = MercurialRepository(safe_str(TEST_HG_REPO))
+        repo = MercurialRepository(TEST_HG_REPO)
         repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
             src_url=TEST_HG_REPO, update_after_clone=True)
         assert len(repo.revisions) == len(repo_clone.revisions)
@@ -42,7 +41,7 @@
             assert raw_id == repo_clone.get_changeset(raw_id).raw_id
 
     def test_repo_clone_with_update(self):
-        repo = MercurialRepository(safe_str(TEST_HG_REPO))
+        repo = MercurialRepository(TEST_HG_REPO)
         repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
             src_url=TEST_HG_REPO, update_after_clone=True)
         assert len(repo.revisions) == len(repo_clone.revisions)
@@ -55,7 +54,7 @@
         )
 
     def test_repo_clone_without_update(self):
-        repo = MercurialRepository(safe_str(TEST_HG_REPO))
+        repo = MercurialRepository(TEST_HG_REPO)
         repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
             src_url=TEST_HG_REPO, update_after_clone=False)
         assert len(repo.revisions) == len(repo_clone.revisions)
@@ -219,7 +218,7 @@
     def test_changeset10(self):
 
         chset10 = self.repo.get_changeset(10)
-        readme = """===
+        readme = b"""===
 VCS
 ===
 
@@ -235,7 +234,7 @@
         assert node.kind == NodeKind.FILE
         assert node.content == readme
 
-    @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
+    @mock.patch('mercurial.mdiff.diffopts')
     def test_get_diff_does_not_sanitize_zero_context(self, mock_diffopts):
         zero_context = 0
 
@@ -243,7 +242,7 @@
 
         mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
 
-    @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
+    @mock.patch('mercurial.mdiff.diffopts')
     def test_get_diff_sanitizes_negative_context(self, mock_diffopts):
         negative_context = -10
         zero_context = 0
@@ -256,7 +255,7 @@
 class TestMercurialChangeset(object):
 
     def setup_method(self):
-        self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
+        self.repo = MercurialRepository(TEST_HG_REPO)
 
     def _test_equality(self, changeset):
         revision = changeset.revision
@@ -444,20 +443,20 @@
         #    added:   20
         #    removed: 1
         changed = set(['.hgignore'
-            , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py'
-            , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py'
-            , 'vcs/backends/__init__.py' , 'vcs/backends/base.py'
-            , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py'])
+            , 'README.rst', 'docs/conf.py', 'docs/index.rst', 'setup.py'
+            , 'tests/test_hg.py', 'tests/test_nodes.py', 'vcs/__init__.py'
+            , 'vcs/backends/__init__.py', 'vcs/backends/base.py'
+            , 'vcs/backends/hg.py', 'vcs/nodes.py', 'vcs/utils/__init__.py'])
 
         added = set(['docs/api/backends/hg.rst'
-            , 'docs/api/backends/index.rst' , 'docs/api/index.rst'
-            , 'docs/api/nodes.rst' , 'docs/api/web/index.rst'
-            , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst'
-            , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py'
-            , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py'
-            , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py'
-            , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py'
-            , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py'
+            , 'docs/api/backends/index.rst', 'docs/api/index.rst'
+            , 'docs/api/nodes.rst', 'docs/api/web/index.rst'
+            , 'docs/api/web/simplevcs.rst', 'docs/installation.rst'
+            , 'docs/quickstart.rst', 'setup.cfg', 'vcs/utils/baseui_config.py'
+            , 'vcs/utils/web.py', 'vcs/web/__init__.py', 'vcs/web/exceptions.py'
+            , 'vcs/web/simplevcs/__init__.py', 'vcs/web/simplevcs/exceptions.py'
+            , 'vcs/web/simplevcs/middleware.py', 'vcs/web/simplevcs/models.py'
+            , 'vcs/web/simplevcs/settings.py', 'vcs/web/simplevcs/utils.py'
             , 'vcs/web/simplevcs/views.py'])
 
         removed = set(['docs/api.rst'])
@@ -536,19 +535,19 @@
         # but it would be one of ``removed`` (changeset's attribute)
         assert path in [rf.path for rf in chset.removed]
 
-    def test_commit_message_is_unicode(self):
+    def test_commit_message_is_str(self):
         for cm in self.repo:
-            assert type(cm.message) == unicode
+            assert isinstance(cm.message, str)
 
-    def test_changeset_author_is_unicode(self):
+    def test_changeset_author_is_str(self):
         for cm in self.repo:
-            assert type(cm.author) == unicode
+            assert isinstance(cm.author, str)
 
-    def test_repo_files_content_is_unicode(self):
+    def test_repo_files_content_is_bytes(self):
         test_changeset = self.repo.get_changeset(100)
         for node in test_changeset.get_node('/'):
             if node.is_file():
-                assert type(node.content) == unicode
+                assert isinstance(node.content, bytes)
 
     def test_wrong_path(self):
         # There is 'setup.py' in the root dir but not there:
--- a/kallithea/tests/vcs/test_inmemchangesets.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_inmemchangesets.py	Thu Feb 06 01:19:23 2020 +0100
@@ -7,11 +7,9 @@
 
 import pytest
 
-from kallithea.lib import vcs
 from kallithea.lib.vcs.exceptions import (
     EmptyRepositoryError, NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError)
 from kallithea.lib.vcs.nodes import DirNode, FileNode
-from kallithea.lib.vcs.utils import safe_unicode
 from kallithea.tests.vcs.base import _BackendTestMixin
 
 
@@ -39,7 +37,7 @@
         for node in to_add:
             self.imc.add(node)
         message = u'Added: %s' % ', '.join((node.path for node in self.nodes))
-        author = unicode(self.__class__)
+        author = str(self.__class__)
         changeset = self.imc.commit(message=message, author=author)
 
         newtip = self.repo.get_changeset()
@@ -61,7 +59,7 @@
             for node in self.nodes]
         self.imc.add(*to_add)
         message = u'Added: %s' % ', '.join((node.path for node in self.nodes))
-        author = unicode(self.__class__)
+        author = str(self.__class__)
         changeset = self.imc.commit(message=message, author=author)
 
         newtip = self.repo.get_changeset()
@@ -83,8 +81,8 @@
         changeset = self.imc.commit(u'Initial', u'joe.doe@example.com')
         assert isinstance(changeset.get_node('foo'), DirNode)
         assert isinstance(changeset.get_node('foo/bar'), DirNode)
-        assert changeset.get_node('foo/bar/image.png').content == '\0'
-        assert changeset.get_node('foo/README.txt').content == 'readme!'
+        assert changeset.get_node('foo/bar/image.png').content == b'\0'
+        assert changeset.get_node('foo/README.txt').content == b'readme!'
 
         # commit some more files again
         to_add = [
@@ -96,11 +94,11 @@
         ]
         self.imc.add(*to_add)
         changeset = self.imc.commit(u'Another', u'joe.doe@example.com')
-        changeset.get_node('foo/bar/foobaz/bar').content == 'foo'
-        changeset.get_node('foo/bar/another/bar').content == 'foo'
-        changeset.get_node('foo/baz.txt').content == 'foo'
-        changeset.get_node('foobar/foobaz/file').content == 'foo'
-        changeset.get_node('foobar/barbaz').content == 'foo'
+        changeset.get_node('foo/bar/foobaz/bar').content == b'foo'
+        changeset.get_node('foo/bar/another/bar').content == b'foo'
+        changeset.get_node('foo/baz.txt').content == b'foo'
+        changeset.get_node('foobar/foobaz/file').content == b'foo'
+        changeset.get_node('foobar/barbaz').content == b'foo'
 
     def test_add_non_ascii_files(self):
         rev_count = len(self.repo.revisions)
@@ -111,7 +109,7 @@
         for node in to_add:
             self.imc.add(node)
         message = u'Added: %s' % ', '.join((node.path for node in self.nodes))
-        author = unicode(self.__class__)
+        author = str(self.__class__)
         changeset = self.imc.commit(message=message, author=author)
 
         newtip = self.repo.get_changeset()
@@ -136,7 +134,7 @@
     def test_check_integrity_raise_already_exist(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
-        self.imc.commit(message=u'Added foobar', author=unicode(self))
+        self.imc.commit(message=u'Added foobar', author=str(self))
         self.imc.add(node)
         with pytest.raises(NodeAlreadyExistsError):
             self.imc.commit(message='new message',
@@ -154,8 +152,8 @@
 
         newtip = self.repo.get_changeset()
         assert tip != newtip
-        assert tip.id != newtip.id
-        assert newtip.get_node('foo/bar/baz').content == 'My **changed** content'
+        assert tip.raw_id != newtip.raw_id
+        assert newtip.get_node('foo/bar/baz').content == b'My **changed** content'
 
     def test_change_non_ascii(self):
         to_add = [
@@ -170,20 +168,18 @@
         # Change node's content
         node = FileNode('żółwik/zwierzątko', content='My **changed** content')
         self.imc.change(node)
-        self.imc.commit(u'Changed %s' % safe_unicode(node.path),
-                        u'joe.doe@example.com')
+        self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com')
 
         node = FileNode(u'żółwik/zwierzątko_uni', content=u'My **changed** content')
         self.imc.change(node)
-        self.imc.commit(u'Changed %s' % safe_unicode(node.path),
-                        u'joe.doe@example.com')
+        self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com')
 
         newtip = self.repo.get_changeset()
         assert tip != newtip
-        assert tip.id != newtip.id
+        assert tip.raw_id != newtip.raw_id
 
-        assert newtip.get_node('żółwik/zwierzątko').content == 'My **changed** content'
-        assert newtip.get_node('żółwik/zwierzątko_uni').content == 'My **changed** content'
+        assert newtip.get_node('żółwik/zwierzątko').content == b'My **changed** content'
+        assert newtip.get_node('żółwik/zwierzątko_uni').content == b'My **changed** content'
 
     def test_change_raise_empty_repository(self):
         node = FileNode('foobar')
@@ -193,7 +189,7 @@
     def test_check_integrity_change_raise_node_does_not_exist(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
-        self.imc.commit(message=u'Added foobar', author=unicode(self))
+        self.imc.commit(message=u'Added foobar', author=str(self))
         node = FileNode('not-foobar', content='')
         self.imc.change(node)
         with pytest.raises(NodeDoesNotExistError):
@@ -202,7 +198,7 @@
     def test_change_raise_node_already_changed(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
-        self.imc.commit(message=u'Added foobar', author=unicode(self))
+        self.imc.commit(message=u'Added foobar', author=str(self))
         node = FileNode('foobar', content='more baz')
         self.imc.change(node)
         with pytest.raises(NodeAlreadyChangedError):
@@ -216,13 +212,13 @@
         with pytest.raises(NodeNotChangedError):
             self.imc.commit(
                 message=u'Trying to mark node as changed without touching it',
-                author=unicode(self)
+                author=str(self),
             )
 
     def test_change_raise_node_already_removed(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
-        self.imc.commit(message=u'Added foobar', author=unicode(self))
+        self.imc.commit(message=u'Added foobar', author=str(self))
         self.imc.remove(FileNode('foobar'))
         with pytest.raises(NodeAlreadyRemovedError):
             self.imc.change(node)
@@ -234,11 +230,11 @@
         node = self.nodes[0]
         assert node.content == tip.get_node(node.path).content
         self.imc.remove(node)
-        self.imc.commit(message=u'Removed %s' % node.path, author=unicode(self))
+        self.imc.commit(message=u'Removed %s' % node.path, author=str(self))
 
         newtip = self.repo.get_changeset()
         assert tip != newtip
-        assert tip.id != newtip.id
+        assert tip.raw_id != newtip.raw_id
         with pytest.raises(NodeDoesNotExistError):
             newtip.get_node(node.path)
 
@@ -257,7 +253,7 @@
         with pytest.raises(NodeDoesNotExistError):
             self.imc.commit(
                 message='Trying to remove node at empty repository',
-                author=str(self)
+                author=str(self),
             )
 
     def test_check_integrity_remove_raise_node_does_not_exist(self):
@@ -268,7 +264,7 @@
         with pytest.raises(NodeDoesNotExistError):
             self.imc.commit(
                 message=u'Trying to remove not existing node',
-                author=unicode(self)
+                author=str(self),
             )
 
     def test_remove_raise_node_already_removed(self):
@@ -301,7 +297,7 @@
     def test_multiple_commits(self):
         N = 3  # number of commits to perform
         last = None
-        for x in xrange(N):
+        for x in range(N):
             fname = 'file%s' % str(x).rjust(5, '0')
             content = 'foobar\n' * x
             node = FileNode(fname, content=content)
--- a/kallithea/tests/vcs/test_nodes.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_nodes.py	Thu Feb 06 01:19:23 2020 +0100
@@ -144,13 +144,13 @@
         assert not mode & stat.S_IXOTH
 
     def test_file_node_is_executable(self):
-        node = FileNode('foobar', 'empty... almost', mode=0100755)
+        node = FileNode('foobar', 'empty... almost', mode=0o100755)
         assert node.is_executable
 
-        node = FileNode('foobar', 'empty... almost', mode=0100500)
+        node = FileNode('foobar', 'empty... almost', mode=0o100500)
         assert node.is_executable
 
-        node = FileNode('foobar', 'empty... almost', mode=0100644)
+        node = FileNode('foobar', 'empty... almost', mode=0o100644)
         assert not node.is_executable
 
     def test_mimetype(self):
@@ -158,10 +158,10 @@
         tar_node = FileNode('test.tar.gz')
 
         my_node2 = FileNode('myfile2')
-        my_node2._content = 'foobar'
+        my_node2._content = b'foobar'
 
         my_node3 = FileNode('myfile3')
-        my_node3._content = '\0foobar'
+        my_node3._content = b'\0foobar'
 
         assert py_node.mimetype == mimetypes.guess_type(py_node.name)[0]
         assert py_node.get_mimetype() == mimetypes.guess_type(py_node.name)
--- a/kallithea/tests/vcs/test_repository.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_repository.py	Thu Feb 06 01:19:23 2020 +0100
@@ -110,7 +110,7 @@
 
     def test_initial_commit_diff(self):
         initial_rev = self.repo.revisions[0]
-        assert self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev) == '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev) == br'''diff --git a/foobar b/foobar
 new file mode 100644
 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
 --- /dev/null
@@ -130,7 +130,7 @@
 
     def test_second_changeset_diff(self):
         revs = self.repo.revisions
-        assert self.repo.get_diff(revs[0], revs[1]) == '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[0], revs[1]) == br'''diff --git a/foobar b/foobar
 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
 --- a/foobar
 +++ b/foobar
@@ -151,7 +151,7 @@
 
     def test_third_changeset_diff(self):
         revs = self.repo.revisions
-        assert self.repo.get_diff(revs[1], revs[2]) == '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[1], revs[2]) == br'''diff --git a/foobar b/foobar
 deleted file mode 100644
 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
 --- a/foobar
@@ -173,7 +173,7 @@
 
     def test_fourth_changeset_diff(self):
         revs = self.repo.revisions
-        assert self.repo.get_diff(revs[2], revs[3]) == '''diff --git a/README{ b/README{
+        assert self.repo.get_diff(revs[2], revs[3]) == br'''diff --git a/README{ b/README{
 new file mode 100644
 index 0000000000000000000000000000000000000000..cdc0c1b5d234feedb37bbac19cd1b6442061102d
 --- /dev/null
@@ -189,7 +189,7 @@
 
     def test_initial_commit_diff(self):
         initial_rev = self.repo.revisions[0]
-        assert self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev) == '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev) == br'''diff --git a/foobar b/foobar
 new file mode 100644
 --- /dev/null
 +++ b/foobar
@@ -207,7 +207,7 @@
 
     def test_second_changeset_diff(self):
         revs = self.repo.revisions
-        assert self.repo.get_diff(revs[0], revs[1]) == '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[0], revs[1]) == br'''diff --git a/foobar b/foobar
 --- a/foobar
 +++ b/foobar
 @@ -1,1 +1,1 @@
@@ -226,7 +226,7 @@
 
     def test_third_changeset_diff(self):
         revs = self.repo.revisions
-        assert self.repo.get_diff(revs[1], revs[2]) == '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[1], revs[2]) == br'''diff --git a/foobar b/foobar
 deleted file mode 100644
 --- a/foobar
 +++ /dev/null
@@ -246,7 +246,7 @@
 
     def test_fourth_changeset_diff(self):
         revs = self.repo.revisions
-        assert self.repo.get_diff(revs[2], revs[3]) == '''diff --git a/README{ b/README{
+        assert self.repo.get_diff(revs[2], revs[3]) == br'''diff --git a/README{ b/README{
 new file mode 100644
 --- /dev/null
 +++ b/README{
--- a/kallithea/tests/vcs/test_vcs.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/kallithea/tests/vcs/test_vcs.py	Thu Feb 06 01:19:23 2020 +0100
@@ -3,7 +3,6 @@
 
 import pytest
 
-from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs import VCSError, get_backend, get_repo
 from kallithea.lib.vcs.backends.hg import MercurialRepository
 from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_HG_REPO, TESTS_TMP_PATH
@@ -22,14 +21,14 @@
         alias = 'hg'
         path = TEST_HG_REPO
         backend = get_backend(alias)
-        repo = backend(safe_str(path))
+        repo = backend(path)
         assert 'hg' == repo.alias
 
     def test_alias_detect_git(self):
         alias = 'git'
         path = TEST_GIT_REPO
         backend = get_backend(alias)
-        repo = backend(safe_str(path))
+        repo = backend(path)
         assert 'git' == repo.alias
 
     def test_wrong_alias(self):
@@ -41,28 +40,28 @@
         alias = 'hg'
         path = TEST_HG_REPO
         backend = get_backend(alias)
-        repo = backend(safe_str(path))
+        repo = backend(path)
 
-        assert repo.__class__ == get_repo(safe_str(path), alias).__class__
-        assert repo.path == get_repo(safe_str(path), alias).path
+        assert repo.__class__ == get_repo(path, alias).__class__
+        assert repo.path == get_repo(path, alias).path
 
     def test_get_repo_autoalias_hg(self):
         alias = 'hg'
         path = TEST_HG_REPO
         backend = get_backend(alias)
-        repo = backend(safe_str(path))
+        repo = backend(path)
 
-        assert repo.__class__ == get_repo(safe_str(path)).__class__
-        assert repo.path == get_repo(safe_str(path)).path
+        assert repo.__class__ == get_repo(path).__class__
+        assert repo.path == get_repo(path).path
 
     def test_get_repo_autoalias_git(self):
         alias = 'git'
         path = TEST_GIT_REPO
         backend = get_backend(alias)
-        repo = backend(safe_str(path))
+        repo = backend(path)
 
-        assert repo.__class__ == get_repo(safe_str(path)).__class__
-        assert repo.path == get_repo(safe_str(path)).path
+        assert repo.__class__ == get_repo(path).__class__
+        assert repo.path == get_repo(path).path
 
     def test_get_repo_err(self):
         blank_repo_path = os.path.join(TESTS_TMP_PATH, 'blank-error-repo')
--- a/scripts/docs-headings.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/docs-headings.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 """
 Consistent formatting of rst section titles
@@ -35,6 +35,7 @@
 def main():
     filenames = subprocess.check_output(['hg', 'loc', 'set:**.rst+kallithea/i18n/how_to']).splitlines()
     for fn in filenames:
+        fn = fn.decode()
         print('processing %s' % fn)
         s = open(fn).read()
 
--- a/scripts/generate-ini.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/generate-ini.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """
 Based on kallithea/lib/paster_commands/template.ini.mako, generate development.ini
 """
--- a/scripts/logformat.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/logformat.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 from __future__ import print_function
 
--- a/scripts/make-release	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/make-release	Thu Feb 06 01:19:23 2020 +0100
@@ -15,11 +15,11 @@
 trap cleanup EXIT
 
 echo "Setting up a fresh virtualenv in $venv"
-virtualenv -p python2 "$venv"
+python3 -m venv "$venv"
 . "$venv/bin/activate"
 
 echo "Install/verify tools needed for building and uploading stuff"
-pip install --upgrade -e . -r dev_requirements.txt twine
+pip install --upgrade -e . -r dev_requirements.txt twine python-ldap python-pam
 
 echo "Cleanup and update copyrights ... and clean checkout"
 scripts/run-all-cleanup
@@ -35,8 +35,8 @@
 sed -e 's/[^ ]*[ ]*\([^ ]*\).*/\1/g' MANIFEST.in | xargs ls -lad
 
 echo "Build dist"
-python2 setup.py compile_catalog
-python2 setup.py sdist
+python3 setup.py compile_catalog
+python3 setup.py sdist
 
 echo "Verify VERSION from kallithea/__init__.py"
 namerel=$(cd dist && echo Kallithea-*.tar.gz)
@@ -46,10 +46,10 @@
 echo "Releasing Kallithea $version in directory $namerel"
 
 echo "Verify dist file content"
-diff -u <((hg mani | grep -v '^\.hg') | LANG=C sort) <(tar tf dist/Kallithea-$version.tar.gz | sed "s|^$namerel/||" | grep . | grep -v '^kallithea/i18n/.*/LC_MESSAGES/kallithea.mo$\|^Kallithea.egg-info/\|^PKG-INFO$\|/$' | LANG=C sort)
+diff -u <((hg mani | grep -v '^\.hg\|^kallithea/i18n/en/LC_MESSAGES/kallithea.mo$') | LANG=C sort) <(tar tf dist/Kallithea-$version.tar.gz | sed "s|^$namerel/||" | grep . | grep -v '^kallithea/i18n/.*/LC_MESSAGES/kallithea.mo$\|^Kallithea.egg-info/\|^PKG-INFO$\|/$' | LANG=C sort)
 
 echo "Verify docs build"
-python2 setup.py build_sphinx # the results are not actually used, but we want to make sure it builds
+python3 setup.py build_sphinx # the results are not actually used, but we want to make sure it builds
 
 echo "Shortlog for inclusion in the release announcement"
 scripts/shortlog.py "only('.', branch('stable') & tagged() & public() & not '.')"
@@ -74,7 +74,7 @@
 echo "Rebuild readthedocs for docs.kallithea-scm.org"
 xdg-open https://readthedocs.org/projects/kallithea/
 curl -X POST http://readthedocs.org/build/kallithea
-xdg-open https://readthedocs.org/builds/kallithea/
+xdg-open https://readthedocs.org/projects/kallithea/builds
 xdg-open http://docs.kallithea-scm.org/en/latest/ # or whatever the branch is
 
 twine upload dist/*
--- a/scripts/shortlog.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/shortlog.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 """
@@ -9,7 +9,7 @@
 import os
 from collections import Counter
 
-import contributor_data
+from . import contributor_data
 
 
 def main():
--- a/scripts/update-copyrights.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/update-copyrights.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 """
@@ -42,7 +42,7 @@
 import re
 from collections import defaultdict
 
-import contributor_data
+from . import contributor_data
 
 
 def sortkey(x):
@@ -51,7 +51,7 @@
     * first contribution
     * number of contribution years
     * name (with some unicode normalization)
-    The entries must be 2-tuples of a list of string years and the unicode name"""
+    The entries must be 2-tuples of a list of string years and the name"""
     return (x[0] and -int(x[0][-1]),
             x[0] and int(x[0][0]),
             -len(x[0]),
@@ -100,6 +100,9 @@
     for year, name in all_entries:
         if name in no_entries or (name, year) in no_entries:
             continue
+        parts = name.split(' <', 1)
+        if len(parts) == 2:
+            name = parts[0] + ' <' + parts[1].lower()
         domain = name.split('@', 1)[-1].rstrip('>')
         if domain in domain_extra:
             name_years[domain_extra[domain]].add(year)
--- a/scripts/validate-commits	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/validate-commits	Thu Feb 06 01:19:23 2020 +0100
@@ -34,7 +34,7 @@
     hg update "$rev"
 
     cleanup
-    virtualenv -p "$(command -v python2)" "$venv"
+    python3 -m venv "$venv"
     source "$venv/bin/activate"
     pip install --upgrade pip setuptools
     pip install -e . -r dev_requirements.txt python-ldap python-pam
--- a/scripts/validate-minimum-dependency-versions	Sun Jan 05 01:19:05 2020 +0100
+++ b/scripts/validate-minimum-dependency-versions	Thu Feb 06 01:19:23 2020 +0100
@@ -28,14 +28,11 @@
 sed -n 's/.*"\(.*\)>=\(.*\)".*/\1==\2/p' setup.py > "$min_requirements"
 sed 's/>=/==/p' dev_requirements.txt >> "$min_requirements"
 
-virtualenv -p "$(command -v python2)" "$venv"
+python3 -m venv "$venv"
 source "$venv/bin/activate"
 pip install --upgrade pip setuptools
 pip install -e . -r "$min_requirements" python-ldap python-pam 2> >(tee "$log" >&2)
 
-# Strip out the known Python 2.7 deprecation message.
-sed -i '/DEPRECATION: Python 2\.7 will reach the end of its life/d' "$log"
-
 # Treat any message on stderr as a problem, for the caller to interpret.
 if [ -s "$log" ]; then
     echo
--- a/setup.py	Sun Jan 05 01:19:05 2020 +0100
+++ b/setup.py	Thu Feb 06 01:19:23 2020 +0100
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 import os
 import platform
@@ -9,8 +9,8 @@
 from setuptools.command import sdist
 
 
-if sys.version_info < (2, 6) or sys.version_info >= (3,):
-    raise Exception('Kallithea requires python 2.7')
+if sys.version_info < (3, 6):
+    raise Exception('Kallithea requires Python 3.6 or later')
 
 
 here = os.path.abspath(os.path.dirname(__file__))
@@ -25,7 +25,7 @@
 
         return callback_handler(eval(matches.groups()[0]))
 
-_meta = open(os.path.join(here, 'kallithea', '__init__.py'), 'rb')
+_meta = open(os.path.join(here, 'kallithea', '__init__.py'), 'r')
 _metadata = _meta.read()
 _meta.close()
 
@@ -40,35 +40,36 @@
 is_windows = __platform__ in ['Windows']
 
 requirements = [
-    "alembic >= 0.8.0, < 1.1",
+    "alembic >= 1.0.10, < 1.1",
     "gearbox >= 0.1.0, < 1",
     "waitress >= 0.8.8, < 1.4",
-    "WebOb >= 1.7, < 1.9",
+    "WebOb >= 1.8, < 1.9",
     "backlash >= 0.1.2, < 1",
-    "TurboGears2 >= 2.3.10, < 2.5",
+    "TurboGears2 >= 2.4, < 2.5",
     "tgext.routes >= 0.2.0, < 1",
-    "Beaker >= 1.7.0, < 2",
-    "WebHelpers >= 1.3, < 1.4",
+    "Beaker >= 1.10.1, < 2",
     "WebHelpers2 >= 2.0, < 2.1",
-    "FormEncode >= 1.3.0, < 1.4",
-    "SQLAlchemy >= 1.1, < 1.4",
-    "Mako >= 0.9.0, < 1.1",
+    "FormEncode >= 1.3.1, < 1.4",
+    "SQLAlchemy >= 1.2.9, < 1.4",
+    "Mako >= 0.9.1, < 1.1",
     "Pygments >= 2.2.0, < 2.5",
-    "Whoosh >= 2.5.0, < 2.8",
+    "Whoosh >= 2.7.1, < 2.8",
     "celery >= 3.1, < 4.0", # TODO: celery 4 doesn't work
     "Babel >= 1.3, < 2.8",
-    "python-dateutil >= 1.5.0, < 2.9",
+    "python-dateutil >= 2.1.0, < 2.9",
     "Markdown >= 2.2.1, < 3.2",
     "docutils >= 0.11, < 0.15",
     "URLObject >= 2.3.4, < 2.5",
-    "Routes >= 1.13, < 2", # TODO: bumping to 2.0 will make test_file_annotation fail
-    "dulwich >= 0.14.1, < 0.20",
-    "mercurial >= 4.5, < 5.3",
-    "decorator >= 3.3.2, < 4.5",
+    "Routes >= 2.0, < 2.5",
+    "dulwich >= 0.19.0, < 0.20",
+    "mercurial >= 5.2, < 5.4",
+    "decorator >= 4.2.1, < 4.5",
     "Paste >= 2.0.3, < 3.1",
     "bleach >= 3.0, < 3.2",
     "Click >= 7.0, < 8",
-    "ipaddr >= 2.1.10, < 2.3",
+    "ipaddr >= 2.2.0, < 2.3",
+    "paginate >= 0.5, < 0.6",
+    "paginate_sqlalchemy >= 0.3.0, < 0.4",
 ]
 
 if not is_windows:
@@ -84,8 +85,9 @@
     'Intended Audience :: Developers',
     'License :: OSI Approved :: GNU General Public License (GPL)',
     'Operating System :: OS Independent',
-    'Programming Language :: Python',
-    'Programming Language :: Python :: 2.7',
+    'Programming Language :: Python :: 3.6',
+    'Programming Language :: Python :: 3.7',
+    'Programming Language :: Python :: 3.8',
     'Topic :: Software Development :: Version Control',
 ]