changeset 5097:4539d652ab08

Merge with stable
author Andrew Shadura <andrew@shadura.me>
date Thu, 07 May 2015 13:30:17 +0200
parents 0c58b6dc5512 (diff) fe3c9c048740 (current diff)
children 6101e27a7799
files kallithea/public/css/style.css
diffstat 83 files changed, 1848 insertions(+), 1594 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu May 07 13:29:49 2015 +0200
+++ b/.hgignore	Thu May 07 13:30:17 2015 +0200
@@ -6,6 +6,8 @@
 *.egg-info
 *.egg
 *.mo
+.eggs/
+tarballcache/
 
 syntax: regexp
 ^rcextensions
--- a/development.ini	Thu May 07 13:29:49 2015 +0200
+++ b/development.ini	Thu May 07 13:30:17 2015 +0200
@@ -134,7 +134,7 @@
 host = 0.0.0.0
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/docs/index.rst	Thu May 07 13:29:49 2015 +0200
+++ b/docs/index.rst	Thu May 07 13:30:17 2015 +0200
@@ -15,6 +15,7 @@
 .. toctree::
    :maxdepth: 1
 
+   overview
    installation
    installation_win
    installation_win_old
--- a/docs/installation.rst	Thu May 07 13:29:49 2015 +0200
+++ b/docs/installation.rst	Thu May 07 13:30:17 2015 +0200
@@ -4,16 +4,11 @@
 Installation on Unix/Linux
 ==========================
 
-**Kallithea** is written entirely in Python_ and requires Python version
-2.6 or higher. Python 3.x is currently not supported.
-
-There are several ways to install Kallithea:
+Here are more details about 3 ways to install Kallithea:
 
-- :ref:`installation-source`: The Kallithea development repository is stable
-  and can be used in production. In fact, the Kallithea maintainers do
-  use it in production. The advantage of installation from source and regularly
-  updating it is that you take advantage of the most recent improvements, which
-  is particularly useful because Kallithea is evolving rapidly.
+- :ref:`installation-source`: The simplest way to keep the installation
+  uptodate and keep track of local customizations is to run directly from
+  source in a Kallithea repository clone and use virtualenv.
 
 - :ref:`installation-virtualenv`: If you prefer to only use released versions
   of Kallithea, the recommended method is to install Kallithea in a virtual
@@ -205,5 +200,4 @@
 
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
-.. _Python: http://www.python.org/
 .. _pylons: http://www.pylonsproject.org/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/overview.rst	Thu May 07 13:30:17 2015 +0200
@@ -0,0 +1,128 @@
+.. _overview:
+
+=====================
+Installation Overview
+=====================
+
+
+Some overview and some details that can help understanding the options when
+installing Kallithea.
+
+
+Python Environment
+------------------
+
+**Kallithea** is written entirely in Python_ and requires Python version
+2.6 or higher. Python 3.x is currently not supported.
+
+Given a Python installation, there are different ways of providing the
+environment for running Python applications. Each of them pretty much
+corresponds to a ``site-packages`` directory somewhere where packages can be
+installed.
+
+Kallithea itself can be run from source or be installed, but even when running
+from source, there are some dependencies that must be installed in the Python
+environment used for running Kallithea.
+
+- Packages *could* be installed in Python's ``site-packages`` directory ... but
+  that would require running pip_ as root and it would be hard to uninstall or
+  upgrade and is probably not a good idea unless using a package manager.
+
+- Packages could also be installed in ``~/.local`` ... but that is probably
+  only a good idea if using a dedicated user per application or instance.
+
+- Finally, it can be installed in a virtualenv_. That is a very lightweight
+  "container" where each Kallithea instance can get its own dedicated and
+  self-contained virtual environment.
+
+We recommend using virtualenv for installing Kallithea.
+
+
+Installation Methods
+--------------------
+
+Kallithea must be installed on a server. Kallithea is installed in a Python
+environment so it can use packages that are installed there and make itself
+available for other packages.
+
+Two different cases will pretty much cover the options for how it can be
+installed.
+
+- The Kallithea source repository can be cloned and used - it is kept stable and
+  can be used in production. The Kallithea maintainers use the development
+  branch in production. The advantage of installation from source and regularly
+  updating it is that you take advantage of the most recent improvements. Using
+  it directly from a DVCS also means that it is easy to track local customizations.
+
+  Running ``setup.py develop`` in the source will use pip to install the
+  necessary dependencies in the Python environment and create a
+  ``.../site-packages/Kallithea.egg-link`` file there that points at the Kallithea
+  source.
+
+- Kallithea can also be installed from ready-made packages using a package manager.
+  The official released versions are available on PyPI_ and can be downloaded and
+  installed with all dependencies using ``pip install kallithea``.
+
+  With this method, Kallithea is installed in the Python environment as any
+  other package, usually as a ``.../site-packages/Kallithea-X-py2.7.egg/``
+  directory with Python files and everything else that is needed.
+
+  (``pip install kallithea`` from a source tree will do pretty much the same
+  but build the Kallithea package itself locally instead of downloading it.)
+
+
+Web Server
+----------
+
+Kallithea is (primarily) a WSGI_ application that must be run from a web
+server that expose WSGI as HTTP.
+
+- Kallithea uses the Paste_ tool for some admin tasks. Paste provides ``paste
+  serve`` as a convenient way to launch Python WSGI / web servers.
+  This method is perfect for development but *can* also be used for production.
+
+  ``paste`` is a command line tool. Using it in production requires some way to
+  wrap it as a managable service.
+
+  Paste come with its own web server but Kallithea defaults to use Waitress_.
+  Gunicorn_ is also an option. These web servers have different limited feature
+  sets.
+
+  It is also common/mandatory to put another web server or (reverse) proxy in
+  front of these Python web servers. Nginx_ is a common choice. This simple
+  setup will thus often end up being quite complex.
+
+  The configuration of which web server to use is in the ini file passed to
+  ``paste``. The entry point for the WSGI application is configured in
+  ``setup.py`` as ``kallithea.config.middleware:make_app``.
+
+- `Apache httpd`_ can serve WSGI applications directly using mod_wsgi_ and a
+  simple Python file with the necessary configuration. This is a good option if
+  Apache is an option.
+
+- IIS_ can also server WSGI applications directly using isapi-wsgi_.
+
+- UWSGI_ is also an option.
+
+The best option depends on what you are familiar with and the requirements for
+performance and stability. Also, keep in mind that Kallithea mainly is serving
+custom data generated from relatively slow Python process. Kallithea is also
+often used inside organizations with a limited amount of users and thus no
+continuous hammering from the internet.
+
+
+.. _Python: http://www.python.org/
+.. _Gunicorn: http://gunicorn.org/
+.. _Waitress: http://waitress.readthedocs.org/en/latest/
+.. _virtualenv: http://pypi.python.org/pypi/virtualenv
+.. _Paste: http://pythonpaste.org/
+.. _PyPI: https://pypi.python.org/pypi
+.. _Apache httpd: http://httpd.apache.org/
+.. _mod_wsgi: https://code.google.com/p/modwsgi/
+.. _isapi-wsgi: https://github.com/hexdump42/isapi-wsgi
+.. _UWSGI: https://uwsgi-docs.readthedocs.org/en/latest/
+.. _nginx: http://nginx.org/en/
+.. _iis: http://en.wikipedia.org/wiki/Internet_Information_Services
+.. _pip: http://en.wikipedia.org/wiki/Pip_%28package_manager%29
+.. _WSGI: http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
+.. _pylons: http://www.pylonsproject.org/
--- a/docs/setup.rst	Thu May 07 13:29:49 2015 +0200
+++ b/docs/setup.rst	Thu May 07 13:30:17 2015 +0200
@@ -68,16 +68,21 @@
   settings, as well as edit more advanced options on users and
   repositories
 
+
+Extensions
+----------
+
 Optionally users can create an ``rcextensions`` package that extends Kallithea
 functionality. To do this simply execute::
 
     paster make-rcext my.ini
 
-This will create an ``rcextensions`` package in the same place that your ``ini`` file
-lives. With ``rcextensions`` it's possible to add additional mapping for whoosh,
+This will create an ``rcextensions`` package next to the specified ``ini`` file.
+With ``rcextensions`` it's possible to add additional mapping for whoosh,
 stats and add additional code into the push/pull/create/delete repo hooks,
-for example, for sending signals to build-bots such as Jenkins.
-Please see the ``__init__.py`` file inside ``rcextensions`` package
+for example for sending signals to build-bots such as Jenkins.
+
+See the ``__init__.py`` file inside the generated ``rcextensions`` package
 for more details.
 
 
--- a/docs/usage/performance.rst	Thu May 07 13:29:49 2015 +0200
+++ b/docs/usage/performance.rst	Thu May 07 13:30:17 2015 +0200
@@ -39,7 +39,8 @@
     sqlite is a good option when having a small load on the system. But due to
     locking issues with sqlite, it is not recommended to use it for larger
     deployments. Switching to mysql or postgres will result in an immediate
-    performance increase.
+    performance increase. A tool like SQLAlchemyGrate_ can be used for
+    migrating to another database platform.
 
 3. Scale Kallithea horizontally
 
@@ -61,3 +62,5 @@
     - Load balance using round robin or IP hash, recommended is writing LB rules
       that will separate regular user traffic from automated processes like CI
       servers or build bots.
+
+.. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate
--- a/kallithea/bin/template.ini.mako	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/bin/template.ini.mako	Thu May 07 13:30:17 2015 +0200
@@ -132,7 +132,7 @@
 host = ${host}
 port = ${port}
 
-<%text>## prefix middleware for rc</%text>
+<%text>## middleware for hosting the WSGI application under a URL prefix</%text>
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/kallithea/config/deployment.ini_tmpl	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/config/deployment.ini_tmpl	Thu May 07 13:30:17 2015 +0200
@@ -129,7 +129,7 @@
 host = 127.0.0.1
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/kallithea/controllers/changeset.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/controllers/changeset.py	Thu May 07 13:30:17 2015 +0200
@@ -349,7 +349,7 @@
     @jsonify
     def comment(self, repo_name, revision):
         status = request.POST.get('changeset_status')
-        text = request.POST.get('text', '').strip() or _('No comments.')
+        text = request.POST.get('text', '').strip()
 
         c.co = comm = ChangesetCommentsModel().create(
             text=text,
--- a/kallithea/controllers/error.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/controllers/error.py	Thu May 07 13:30:17 2015 +0200
@@ -32,7 +32,7 @@
 
 from pylons import tmpl_context as c, request, config
 from pylons.i18n.translation import _
-from pylons.middleware import  media_path
+from pylons.middleware import media_path
 
 from kallithea.lib.base import BaseController, render
 
@@ -50,7 +50,7 @@
     """
 
     def __before__(self):
-        #disable all base actions since we don't need them here
+        # disable all base actions since we don't need them here
         pass
 
     def document(self):
@@ -60,12 +60,16 @@
         log.debug('### %s ###' % (resp and resp.status or 'no response'))
 
         e = request.environ
-        c.serv_p = r'%(protocol)s://%(host)s/' \
-                                    % {'protocol': e.get('wsgi.url_scheme'),
-                                       'host': e.get('HTTP_HOST'), }
-
-        c.error_message = resp and cgi.escape(request.GET.get('code', str(resp.status)))
-        c.error_explanation = resp and self.get_error_explanation(resp.status_int)
+        c.serv_p = r'%(protocol)s://%(host)s/' % {
+            'protocol': e.get('wsgi.url_scheme'),
+            'host': e.get('HTTP_HOST'), }
+        if resp:
+            c.error_message = cgi.escape(request.GET.get('code',
+                                                         str(resp.status)))
+            c.error_explanation = self.get_error_explanation(resp.status_int)
+        else:
+            c.error_message = _('No response')
+            c.error_explanation = _('Unknown error')
 
         return render('/errors/error_document.html')
 
--- a/kallithea/controllers/pullrequests.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/controllers/pullrequests.py	Thu May 07 13:30:17 2015 +0200
@@ -696,7 +696,7 @@
         if allowed_to_change_status:
             status = request.POST.get('changeset_status')
             close_pr = request.POST.get('save_close')
-        text = request.POST.get('text', '').strip() or _('No comments.')
+        text = request.POST.get('text', '').strip()
         if close_pr:
             text = _('Closing.') + '\n' + text
 
--- a/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Thu May 07 13:30:17 2015 +0200
@@ -8,14 +8,16 @@
 "Project-Id-Version: Kallithea\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
 "POT-Creation-Date: 2015-04-01 03:17+0200\n"
-"PO-Revision-Date: 2014-07-02 19:08-0400\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\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/kallithea/hu/>\n"
+"Language: hu\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: hu\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2.3-dev\n"
 
 #: kallithea/controllers/changelog.py:86
 #: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:449
@@ -55,9 +57,8 @@
 
 #: kallithea/controllers/changeset.py:352
 #: kallithea/controllers/pullrequests.py:699
-#, fuzzy
 msgid "No comments."
-msgstr ""
+msgstr "Nincsenek hozzászólások."
 
 #: kallithea/controllers/changeset.py:382
 msgid ""
@@ -2351,11 +2352,11 @@
 
 #: kallithea/templates/admin/admin.html:13
 #: kallithea/templates/journal/journal.html:12
-#, fuzzy, python-format
+#, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%s bejegyzés"
+msgstr[1] "%s bejegyzés"
 
 #: kallithea/templates/admin/admin_log.html:6
 #: kallithea/templates/admin/my_account/my_account_repos.html:50
@@ -3246,9 +3247,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
-#, fuzzy
 msgid "Confirm to delete this group"
-msgstr ""
+msgstr "A csoport törlésének megerősítése"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
@@ -3335,9 +3335,9 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
-msgstr ""
+msgstr "%s tároló beállítások"
 
 #: kallithea/templates/admin/repos/repo_edit.html:49
 msgid "Extra Fields"
@@ -3429,16 +3429,15 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:82
-#, fuzzy
 msgid "Delete this Repository"
-msgstr ""
+msgstr "Tároló törlése"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:85
-#, fuzzy, python-format
+#, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Ennek a tárolónak %s elágazása van"
+msgstr[1] "Ennek a tárolónak %s elágazása van"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:86
 msgid "Detach forks"
@@ -4437,9 +4436,8 @@
 msgstr ""
 
 #: kallithea/templates/base/root.html:22
-#, fuzzy
 msgid "Add Another Comment"
-msgstr ""
+msgstr "Egy másik hozzászólás hozzáadása"
 
 #: kallithea/templates/base/root.html:23
 #: kallithea/templates/data_table/_dt_elements.html:216
@@ -4809,9 +4807,8 @@
 msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:50
-#, fuzzy
 msgid "Delete comment?"
-msgstr ""
+msgstr "Hozzászólás törlése?"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:67
 msgid "Commenting on line {1}."
@@ -4866,11 +4863,11 @@
 msgstr[1] ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:115
-#, fuzzy, python-format
+#, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d sorközi"
+msgstr[1] "%d sorközi"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:116
 #, python-format
@@ -4957,9 +4954,9 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_cs.html:84
-#, fuzzy, python-format
+#, python-format
 msgid "%s changesets"
-msgstr ""
+msgstr "%s módosításcsomag"
 
 #: kallithea/templates/compare/compare_cs.html:85
 msgid "behind"
@@ -5575,14 +5572,12 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:227
-#, fuzzy
 msgid "Remove reviewer"
-msgstr ""
+msgstr "Átnéző eltávolítása"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:247
-#, fuzzy
 msgid "Potential Reviewers"
-msgstr ""
+msgstr "Lehetséges átnézők"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:250
 msgid "Click to add the repository owner as reviewer:"
@@ -5841,4 +5836,3 @@
 #: kallithea/templates/tags/tags.html:26
 msgid "Compare Tags"
 msgstr ""
-
--- a/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Thu May 07 13:30:17 2015 +0200
@@ -12,18 +12,20 @@
 # SkryabinD <skryabind@gmail.com>, 2014
 # softforwinxp <softforwinxp@gmail.com>, 2013
 # zhmylove <zhmylove@narod.ru>, 2013
+# Andrew Shadura <andrew@shadura.me>, 2015
+#
 msgid ""
 msgstr ""
 "Project-Id-Version: Kallithea\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
 "POT-Creation-Date: 2015-04-01 03:17+0200\n"
-"PO-Revision-Date: 2015-04-01 13:08+0200\n"
+"PO-Revision-Date: 2015-04-13 20:18+0200\n"
 "Last-Translator: Andrew Shadura <andrew@shadura.me>\n"
 "Language-Team: Russian "
 "<https://hosted.weblate.org/projects/kallithea/kallithea/ru/>\n"
 "Language: ru\n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\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"
@@ -84,7 +86,7 @@
 
 #: kallithea/controllers/compare.py:255
 msgid "Cannot compare repositories without using common ancestor"
-msgstr ""
+msgstr "Невозможно сравнивать репозитории без общего предка"
 
 #: kallithea/controllers/error.py:96
 msgid "The request could not be understood by the server due to malformed syntax."
@@ -221,7 +223,7 @@
 
 #: kallithea/controllers/files.py:541
 msgid "Empty repository"
-msgstr "Пустой репозитарий"
+msgstr "Пустой репозиторий"
 
 #: kallithea/controllers/files.py:543
 msgid "Unknown archive type"
@@ -357,12 +359,12 @@
 
 #: kallithea/controllers/pullrequests.py:398
 msgid "Missing changesets since the previous pull request:"
-msgstr ""
+msgstr "Отсутствующие ревизии относительно предыдущего pull-запроса:"
 
 #: kallithea/controllers/pullrequests.py:405
 #, python-format
 msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
+msgstr "Новые ревизии на %s %s относительно предыдущего pull-запроса"
 
 #: kallithea/controllers/pullrequests.py:412
 msgid "Ancestor didn't change - show diff since previous version:"
@@ -373,17 +375,17 @@
 msgid ""
 "This pull request is based on another %s revision and there is no simple "
 "diff."
-msgstr ""
+msgstr "Этот pull-запрос основан на другой ревизии %s, простой diff невозможен"
 
 #: kallithea/controllers/pullrequests.py:421
 #, python-format
 msgid "No changes found on %s %s since previous version."
-msgstr ""
+msgstr "Нет изменений на %s %s относительно предыдущей версии."
 
 #: kallithea/controllers/pullrequests.py:456
 #, python-format
 msgid "Closed, replaced by %s ."
-msgstr ""
+msgstr "Закрыт, замещён %s ."
 
 #: kallithea/controllers/pullrequests.py:464
 msgid "Pull request update created"
@@ -400,29 +402,29 @@
 #: kallithea/controllers/pullrequests.py:577
 #, python-format
 msgid "This pull request has already been merged to %s."
-msgstr ""
+msgstr "Этот pull-запрос уже принят на ветку %s."
 
 #: kallithea/controllers/pullrequests.py:579
 msgid "This pull request has been closed and can not be updated."
-msgstr ""
+msgstr "Этот pull-запрос был закрыт и не может быть обновлён."
 
 #: kallithea/controllers/pullrequests.py:597
 #, python-format
 msgid "This pull request can be updated with changes on %s:"
-msgstr ""
+msgstr "Этот pull-запрос может быть обновлён из %s:"
 
 #: kallithea/controllers/pullrequests.py:600
 msgid "No changesets found for updating this pull request."
-msgstr ""
+msgstr "Нет изменений для обновления этого pull-запроса."
 
 #: kallithea/controllers/pullrequests.py:608
 #, python-format
 msgid "Note: Branch %s has another head: %s."
-msgstr ""
+msgstr "Внимание: Ветка %s имеет ещё одну верхушку: %s."
 
 #: kallithea/controllers/pullrequests.py:614
 msgid "Git pull requests don't support updates yet."
-msgstr ""
+msgstr "Обновление pull-запросы git не поддерживается."
 
 #: kallithea/controllers/pullrequests.py:701
 msgid "Closing."
@@ -521,12 +523,12 @@
 
 #: kallithea/controllers/admin/gists.py:267
 msgid "Successfully updated gist data"
-msgstr ""
+msgstr "Данные gist-записи обновлены"
 
 #: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
-msgstr ""
+msgstr "Произошла ошибка при обновлении gist-записи %s"
 
 #: kallithea/controllers/admin/my_account.py:70
 msgid "You can't edit this user since it's crucial for entire application"
@@ -571,17 +573,17 @@
 #: kallithea/controllers/admin/my_account.py:254
 #: kallithea/controllers/admin/users.py:314
 msgid "Api key successfully created"
-msgstr ""
+msgstr "API-ключ успешно создан"
 
 #: kallithea/controllers/admin/my_account.py:266
 #: kallithea/controllers/admin/users.py:330
 msgid "Api key successfully reset"
-msgstr ""
+msgstr "API-ключ успешно сброшен"
 
 #: kallithea/controllers/admin/my_account.py:270
 #: kallithea/controllers/admin/users.py:334
 msgid "Api key successfully deleted"
-msgstr ""
+msgstr "API-ключ успешно удалён"
 
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
@@ -805,7 +807,7 @@
 
 #: kallithea/controllers/admin/repos.py:492
 msgid "-- Not a fork --"
-msgstr ""
+msgstr "-- Не форк --"
 
 #: kallithea/controllers/admin/repos.py:526
 msgid "Updated repository visibility in public journal"
@@ -860,7 +862,7 @@
 
 #: kallithea/controllers/admin/repos.py:622
 msgid "Cache invalidation successful"
-msgstr ""
+msgstr "Кэш сброшен"
 
 #: kallithea/controllers/admin/repos.py:626
 msgid "An error occurred during cache invalidation"
@@ -1045,7 +1047,7 @@
 
 #: kallithea/lib/base.py:427
 msgid "Repository not found in the filesystem"
-msgstr ""
+msgstr "Репозиторий не найден на файловой системе"
 
 #: kallithea/lib/base.py:453 kallithea/lib/helpers.py:643
 msgid "Changeset not found"
@@ -2079,7 +2081,7 @@
 
 #: kallithea/templates/about.html:4 kallithea/templates/about.html:17
 msgid "About"
-msgstr ""
+msgstr "О программе"
 
 #: kallithea/templates/index.html:5
 msgid "Dashboard"
@@ -2316,7 +2318,7 @@
 #: kallithea/templates/password_reset.html:35
 #: kallithea/templates/register.html:79
 msgid "Captcha"
-msgstr ""
+msgstr "Капча"
 
 #: kallithea/templates/password_reset.html:46
 msgid "Send Password Reset Email"
@@ -2331,12 +2333,12 @@
 #: kallithea/templates/register.html:5 kallithea/templates/register.html:14
 #: kallithea/templates/register.html:90
 msgid "Sign Up"
-msgstr "Вступить"
+msgstr "Регистрация"
 
 #: kallithea/templates/register.html:12
 #, python-format
 msgid "Sign Up to %s"
-msgstr ""
+msgstr "Регистра на %s"
 
 #: kallithea/templates/register.html:42
 msgid "Re-enter password"
@@ -2372,7 +2374,7 @@
 
 #: kallithea/templates/register.html:94
 msgid "Please wait for an administrator to activate your account."
-msgstr ""
+msgstr "Пожалуйста, подождите, пока администратор подтвердит Вашу регистрацию."
 
 #: kallithea/templates/switch_to_list.html:10
 #: kallithea/templates/branches/branches_data.html:69
@@ -2433,7 +2435,7 @@
 #: kallithea/templates/admin/admin_log.html:7
 #: kallithea/templates/admin/permissions/permissions_globals.html:18
 msgid "Repository"
-msgstr "Репозитарий"
+msgstr "Репозиторий"
 
 #: kallithea/templates/admin/admin_log.html:8
 #: kallithea/templates/bookmarks/bookmarks.html:51
@@ -2485,12 +2487,12 @@
 #: kallithea/templates/admin/auth/auth_settings.html:40
 #: kallithea/templates/base/root.html:40
 msgid "enabled"
-msgstr ""
+msgstr "включено"
 
 #: kallithea/templates/admin/auth/auth_settings.html:40
 #: kallithea/templates/base/root.html:41
 msgid "disabled"
-msgstr ""
+msgstr "отключено"
 
 #: kallithea/templates/admin/auth/auth_settings.html:51
 msgid "Plugin"
@@ -2556,7 +2558,7 @@
 #: kallithea/templates/admin/defaults/defaults.html:59
 #: kallithea/templates/admin/repos/repo_edit_settings.html:95
 msgid "Enable statistics window on summary page."
-msgstr "Включить окно статистики на странице 'Общие сведения'."
+msgstr "Включить окно статистики на странице «Общие сведения»."
 
 #: kallithea/templates/admin/defaults/defaults.html:65
 #: kallithea/templates/admin/repos/repo_edit_settings.html:100
@@ -2566,7 +2568,7 @@
 #: kallithea/templates/admin/defaults/defaults.html:69
 #: kallithea/templates/admin/repos/repo_edit_settings.html:104
 msgid "Enable download menu on summary page."
-msgstr "Включить меню скачивания на странице  'Общие сведения'."
+msgstr "Включить меню скачивания на странице «Общие сведения»."
 
 #: kallithea/templates/admin/defaults/defaults.html:75
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
@@ -2582,7 +2584,7 @@
 #: kallithea/templates/admin/gists/edit.html:5
 #: kallithea/templates/admin/gists/edit.html:18
 msgid "Edit Gist"
-msgstr ""
+msgstr "Правка gist-записи"
 
 #: kallithea/templates/admin/gists/edit.html:36
 #, python-format
@@ -2622,7 +2624,7 @@
 
 #: kallithea/templates/admin/gists/edit.html:145
 msgid "Update Gist"
-msgstr ""
+msgstr "Обновить"
 
 #: kallithea/templates/admin/gists/edit.html:146
 #: kallithea/templates/changeset/changeset_file_comment.html:89
@@ -2633,24 +2635,24 @@
 #: kallithea/templates/admin/gists/index.html:16
 #, python-format
 msgid "Private Gists for User %s"
-msgstr "Приватная запись Gist для пользователя %s"
+msgstr "Приватная gist-запись для пользователя %s"
 
 #: kallithea/templates/admin/gists/index.html:8
 #: kallithea/templates/admin/gists/index.html:18
 #, python-format
 msgid "Public Gists for User %s"
-msgstr "Публичная запись Gist для пользователя %s"
+msgstr "Публичная gist-запись для пользователя %s"
 
 #: kallithea/templates/admin/gists/index.html:10
 #: kallithea/templates/admin/gists/index.html:20
 msgid "Public Gists"
-msgstr "Публичные записи Gist"
+msgstr "Публичные gist-записи"
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
 #: kallithea/templates/base/base.html:240
 msgid "Create New Gist"
-msgstr ""
+msgstr "Создать новую gist-запись"
 
 #: kallithea/templates/admin/gists/index.html:54
 #: kallithea/templates/data_table/_dt_elements.html:143
@@ -2659,7 +2661,7 @@
 
 #: kallithea/templates/admin/gists/index.html:74
 msgid "There are no gists yet"
-msgstr "Записи Gist отсутствуют"
+msgstr "Gist-записи отсутствуют"
 
 #: kallithea/templates/admin/gists/new.html:5
 #: kallithea/templates/admin/gists/new.html:18
@@ -2737,7 +2739,7 @@
 
 #: kallithea/templates/admin/gists/show.html:56
 msgid "Confirm to delete this Gist"
-msgstr ""
+msgstr "Подтвердите удаление этой gist-записи"
 
 #: kallithea/templates/admin/gists/show.html:63
 #: kallithea/templates/changeset/changeset_file_comment.html:91
@@ -2755,16 +2757,16 @@
 #: kallithea/templates/files/files_edit.html:49
 #: kallithea/templates/files/files_source.html:34
 msgid "Show as Raw"
-msgstr ""
+msgstr "Показать только текст"
 
 #: kallithea/templates/admin/gists/show.html:73
 msgid "created"
-msgstr "создал"
+msgstr "создана"
 
 #: kallithea/templates/admin/gists/show.html:86
 #: kallithea/templates/files/files_source.html:73
 msgid "Show as raw"
-msgstr "Показать без форматирования"
+msgstr "Показать только текст"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
@@ -2780,11 +2782,11 @@
 #: kallithea/templates/admin/my_account/my_account.html:37
 #: kallithea/templates/admin/users/user_edit.html:30
 msgid "API Keys"
-msgstr ""
+msgstr "API-ключи"
 
 #: kallithea/templates/admin/my_account/my_account.html:38
 msgid "My Emails"
-msgstr "Мои Email-ы"
+msgstr "Мои адреса E-mail"
 
 #: kallithea/templates/admin/my_account/my_account.html:39
 msgid "My Repositories"
@@ -2817,7 +2819,7 @@
 #: kallithea/templates/admin/users/user_edit_api_keys.html:14
 #, python-format
 msgid "Confirm to reset this api key: %s"
-msgstr ""
+msgstr "Подтвердите сброс этого API-ключа: %s"
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:15
 #: kallithea/templates/admin/users/user_edit_api_keys.html:15
@@ -2833,7 +2835,7 @@
 #: kallithea/templates/admin/users/user_edit_api_keys.html:40
 #, python-format
 msgid "Confirm to remove this api key: %s"
-msgstr ""
+msgstr "Подтвердите удаление этого API-ключа: %s"
 
 #: kallithea/templates/admin/my_account/my_account_api_keys.html:42
 #: kallithea/templates/admin/users/user_edit_api_keys.html:42
@@ -2901,7 +2903,7 @@
 
 #: kallithea/templates/admin/my_account/my_account_password.html:7
 msgid "Current password"
-msgstr ""
+msgstr "Текущий пароль"
 
 #: kallithea/templates/admin/my_account/my_account_password.html:16
 #: kallithea/templates/admin/users/user_edit_profile.html:69
@@ -2910,7 +2912,7 @@
 
 #: kallithea/templates/admin/my_account/my_account_password.html:25
 msgid "Confirm new password"
-msgstr ""
+msgstr "Подтвердите новый пароль"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:11
 msgid "Change your avatar at"
@@ -2933,7 +2935,7 @@
 #: kallithea/templates/admin/my_account/my_account_profile.html:16
 #: kallithea/templates/admin/users/user_edit_profile.html:15
 msgid "current IP"
-msgstr ""
+msgstr "текущий IP-адрес"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:28
 msgid ""
@@ -3039,9 +3041,8 @@
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
-"Выбранные привилегии будут установлены по умолчанию для каждого "
-"репозитория. Учтите, что ранее установленные привилегии по умолчанию "
-"будут сброшены"
+"Выбранные привилегии будут установлены по умолчанию для каждого репозитория. "
+"Учтите, что ранее установленные привилегии по умолчанию будут сброшены"
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
 #: kallithea/templates/admin/permissions/permissions_globals.html:40
@@ -3064,8 +3065,8 @@
 "will be lost"
 msgstr ""
 "Выбранные привилегии будут установлены по умолчанию для каждой группы "
-"репозиториев. Учтите, что ранее установленные привилегии по умолчанию для"
-" групп репозиториев будут сброшены"
+"репозиториев. Учтите, что ранее установленные привилегии по умолчанию для "
+"групп репозиториев будут сброшены"
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:46
 #: kallithea/templates/data_table/_dt_elements.html:211
@@ -3079,8 +3080,8 @@
 "will be lost"
 msgstr ""
 "Выбранные привилегии будут установлены по умолчанию для каждой группы "
-"пользователей. Учтите, что ранее установленные привилегии по умолчанию "
-"для групп пользователей будут сброшены"
+"пользователей. Учтите, что ранее установленные привилегии по умолчанию для "
+"групп пользователей будут сброшены"
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:60
 msgid "Repository creation"
@@ -3120,7 +3121,7 @@
 #: kallithea/templates/admin/users/user_edit_ips.html:23
 #, python-format
 msgid "Confirm to delete this ip: %s"
-msgstr "Подтвердите удаление ip %s"
+msgstr "Подтвердите удаление IP %s"
 
 #: kallithea/templates/admin/permissions/permissions_ips.html:21
 #: kallithea/templates/admin/users/user_edit_ips.html:30
@@ -3130,11 +3131,11 @@
 #: kallithea/templates/admin/permissions/permissions_ips.html:32
 #: kallithea/templates/admin/users/user_edit_ips.html:42
 msgid "New ip address"
-msgstr "Новый ip-адрес"
+msgstr "Новый IP-адрес"
 
 #: kallithea/templates/admin/permissions/permissions_perms.html:1
 msgid "Default User Permissions Overview"
-msgstr "Обзор прав пользователей по-умолчанию"
+msgstr "Обзор прав пользователей по умолчанию"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
@@ -3169,7 +3170,7 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
 #, python-format
 msgid "%s Repository Group Settings"
-msgstr "Настройки рруппы репозиториев %s"
+msgstr "Настройки группы репозиториев %s"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
 msgid "Add Child Group"
@@ -3228,7 +3229,7 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25
 msgid "Delete this repository group"
-msgstr ""
+msgstr "Удалить эту группу репозиториев"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 #: kallithea/templates/admin/repos/repo_edit_permissions.html:8
@@ -3271,7 +3272,7 @@
 #: 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 "по-умолчанию"
+msgstr "по умолчанию"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
@@ -3312,7 +3313,7 @@
 "Enable lock-by-pulling on group. This option will be applied to all other"
 " groups and repositories inside"
 msgstr ""
-"Включить lock-by-pulling для группы. Эта опция будет применена ко всем "
+"Включить автоблокировку для группы. Эта опция будет применена ко всем "
 "дочерним группам и репозиториям"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
@@ -3361,7 +3362,8 @@
 #: kallithea/templates/admin/repos/repo_edit_settings.html:76
 #: kallithea/templates/forks/fork.html:42
 msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr "Короткое и осмысленное. Для развернутого описания используйте файл README."
+msgstr ""
+"Короткое и осмысленное. Для развернутого описания используйте файл README."
 
 #: kallithea/templates/admin/repos/repo_add_base.html:45
 #: kallithea/templates/admin/repos/repo_edit_settings.html:46
@@ -4963,7 +4965,7 @@
 msgstr[2] "%d к строкам"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:116
-#, fuzzy, python-format
+#, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
--- a/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Thu May 07 13:30:17 2015 +0200
@@ -1239,7 +1239,7 @@
                 pass
 
         return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualifed_home_url=qualified_home_url,
+                             qualified_home_url=qualified_home_url,
                              repo_name=self.repo_name,
                              repo_id=self.repo_id, **override)
 
--- a/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Thu May 07 13:30:17 2015 +0200
@@ -1263,7 +1263,7 @@
                 pass
 
         return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualifed_home_url=qualified_home_url,
+                             qualified_home_url=qualified_home_url,
                              repo_name=self.repo_name,
                              repo_id=self.repo_id, **override)
 
--- a/kallithea/lib/helpers.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/helpers.py	Thu May 07 13:30:17 2015 +0200
@@ -423,7 +423,7 @@
 #==============================================================================
 from kallithea.lib.vcs.utils import author_name, author_email
 from kallithea.lib.utils2 import credentials_filter, age as _age
-from kallithea.model.db import User, ChangesetStatus
+from kallithea.model.db import User, ChangesetStatus, PullRequest
 
 age = lambda  x, y=False: _age(x, y)
 capitalize = lambda x: x.capitalize()
@@ -736,12 +736,15 @@
 
     def get_pull_request():
         pull_request_id = action_params
+        nice_id = PullRequest.make_nice_id(pull_request_id)
+
         deleted = user_log.repository is None
         if deleted:
             repo_name = user_log.repository_name
         else:
             repo_name = user_log.repository.repo_name
-        return link_to(_('Pull request #%s') % pull_request_id,
+
+        return link_to(_('Pull request %s') % nice_id,
                     url('pullrequest_show', repo_name=repo_name,
                     pull_request_id=pull_request_id))
 
--- a/kallithea/lib/paster_commands/make_rcextensions.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/paster_commands/make_rcextensions.py	Thu May 07 13:30:17 2015 +0200
@@ -30,7 +30,6 @@
 
 import os
 import sys
-import logging
 import pkg_resources
 
 from kallithea.lib.utils import BasePasterCommand, ask_ok
@@ -40,46 +39,44 @@
 rc_path = dn(dn(dn(os.path.realpath(__file__))))
 sys.path.append(rc_path)
 
-log = logging.getLogger(__name__)
-
 
 class Command(BasePasterCommand):
 
     max_args = 1
     min_args = 1
 
-    usage = "CONFIG_FILE"
     group_name = "Kallithea"
     takes_config_file = -1
     parser = BasePasterCommand.standard_parser(verbose=True)
-    summary = "Creates additional extensions for kallithea"
+    summary = "Write template file for extending Kallithea in Python."
+    usage = "CONFIG_FILE"
+    description = '''\
+        A rcextensions directory with a __init__.py file will be created next to
+        the ini file. Local customizations in that file will survive upgrades.
+        The file contains instructions on how it can be customized.
+        '''
 
     def command(self):
-        logging.config.fileConfig(self.path_to_ini_file)
         from pylons import config
 
-        def _make_file(ext_file, tmpl):
-            bdir = os.path.split(ext_file)[0]
-            if not os.path.isdir(bdir):
-                os.makedirs(bdir)
-            with open(ext_file, 'wb') as f:
-                f.write(tmpl)
-                log.info('Writen new extensions file to %s' % ext_file)
-
         here = config['here']
-        tmpl = pkg_resources.resource_string(
+        content = pkg_resources.resource_string(
             'kallithea', os.path.join('config', 'rcextensions', '__init__.py')
         )
         ext_file = os.path.join(here, 'rcextensions', '__init__.py')
         if os.path.exists(ext_file):
             msg = ('Extension file already exists, do you want '
                    'to overwrite it ? [y/n]')
-            if ask_ok(msg):
-                _make_file(ext_file, tmpl)
-            else:
-                log.info('nothing done...')
-        else:
-            _make_file(ext_file, tmpl)
+            if not ask_ok(msg):
+                print 'Nothing done...'
+                return
+
+        dirname = os.path.dirname(ext_file)
+        if not os.path.isdir(dirname):
+            os.makedirs(dirname)
+        with open(ext_file, 'wb') as f:
+            f.write(content)
+            print 'Wrote new extensions file to %s' % ext_file
 
     def update_parser(self):
         pass
--- a/kallithea/lib/utils2.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/utils2.py	Thu May 07 13:30:17 2015 +0200
@@ -503,8 +503,8 @@
     return ''.join(uri)
 
 
-def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
-    parsed_url = urlobject.URLObject(qualifed_home_url)
+def get_clone_url(uri_tmpl, qualified_home_url, repo_name, repo_id, **override):
+    parsed_url = urlobject.URLObject(qualified_home_url)
     decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
     args = {
         'scheme': parsed_url.scheme,
@@ -625,25 +625,25 @@
 
 def _extract_extras(env=None):
     """
-    Extracts the rc extras data from os.environ, and wraps it into named
+    Extracts the Kallithea extras data from os.environ, and wraps it into named
     AttributeDict object
     """
     if not env:
         env = os.environ
 
     try:
-        rc_extras = json.loads(env['KALLITHEA_EXTRAS'])
+        extras = json.loads(env['KALLITHEA_EXTRAS'])
     except KeyError:
-        rc_extras = {}
+        extras = {}
 
     try:
         for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
                   'action', 'ip']:
-            rc_extras[k]
+            extras[k]
     except KeyError, e:
-        raise Exception('Missing key %s in os.environ %s' % (e, rc_extras))
+        raise Exception('Missing key %s in os.environ %s' % (e, extras))
 
-    return AttributeDict(rc_extras)
+    return AttributeDict(extras)
 
 
 def _set_extras(extras):
--- a/kallithea/lib/vcs/backends/hg/repository.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Thu May 07 13:30:17 2015 +0200
@@ -229,7 +229,7 @@
 
     def _get_all_revisions(self):
 
-        return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
+        return [self._repo[x].hex() for x in self._repo.filtered('visible').changelog.revs()]
 
     def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
                   context=3):
@@ -264,6 +264,7 @@
 
         return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
                           opts=diffopts(git=True,
+                                        showfunc=True,
                                         ignorews=ignore_whitespace,
                                         context=context)))
 
--- a/kallithea/lib/vcs/utils/lazy.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/lib/vcs/utils/lazy.py	Thu May 07 13:30:17 2015 +0200
@@ -1,3 +1,6 @@
+import threading
+
+
 class _Missing(object):
 
     def __repr__(self):
@@ -41,8 +44,6 @@
             obj.__dict__[self.__name__] = value
         return value
 
-import threading
-
 
 class ThreadLocalLazyProperty(LazyProperty):
     """
--- a/kallithea/model/changeset_status.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/model/changeset_status.py	Thu May 07 13:30:17 2015 +0200
@@ -66,9 +66,27 @@
         q = q.order_by(ChangesetStatus.version.asc())
         return q
 
+    def _calculate_status(self, statuses):
+        """
+        Given a list of statuses, calculate the resulting status, according to
+        the policy: approve if consensus, reject when at least one reject.
+        """
+
+        if not statuses:
+            return ChangesetStatus.STATUS_UNDER_REVIEW
+
+        if all(st and st.status == ChangesetStatus.STATUS_APPROVED for st in statuses):
+            return ChangesetStatus.STATUS_APPROVED
+
+        if any(st and st.status == ChangesetStatus.STATUS_REJECTED for st in statuses):
+            return ChangesetStatus.STATUS_REJECTED
+
+        return ChangesetStatus.STATUS_UNDER_REVIEW
+
     def calculate_pull_request_result(self, pull_request):
         """
-        Policy: approve if consensus. Only approve and reject counts as valid votes.
+        Return a tuple (reviewers, pending reviewers, pull request status)
+        Only approve and reject counts as valid votes.
         """
 
         # collect latest votes from all voters
@@ -77,24 +95,21 @@
                                              pull_request=pull_request,
                                              with_revisions=True)):
             cs_statuses[st.author.username] = st
+
         # collect votes from official reviewers
         pull_request_reviewers = []
         pull_request_pending_reviewers = []
-        approved_votes = 0
+        relevant_statuses = []
         for r in pull_request.reviewers:
             st = cs_statuses.get(r.user.username)
-            if st and st.status == ChangesetStatus.STATUS_APPROVED:
-                approved_votes += 1
+            relevant_statuses.append(st)
             if not st or st.status in (ChangesetStatus.STATUS_NOT_REVIEWED,
                                        ChangesetStatus.STATUS_UNDER_REVIEW):
                 st = None
                 pull_request_pending_reviewers.append(r.user)
             pull_request_reviewers.append((r.user, st))
 
-        # calculate result
-        result = ChangesetStatus.STATUS_UNDER_REVIEW
-        if approved_votes and approved_votes == len(pull_request.reviewers):
-            result = ChangesetStatus.STATUS_APPROVED
+        result = self._calculate_status(relevant_statuses)
 
         return (pull_request_reviewers,
                 pull_request_pending_reviewers,
--- a/kallithea/model/comment.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/model/comment.py	Thu May 07 13:30:17 2015 +0200
@@ -63,10 +63,6 @@
                                line_no=None, revision=None, pull_request=None,
                                status_change=None, closing_pr=False):
         """
-        Get notification data
-
-        :param comment_text:
-        :param line:
         :returns: tuple (subj,body,recipients,notification_type,email_kwargs)
         """
         # make notification
@@ -130,9 +126,9 @@
             comment_url = pull_request.url(canonical=True,
                 anchor='comment-%s' % comment.comment_id)
             subj = safe_unicode(
-                h.link_to('Re pull request #%(pr_id)s: %(desc)s %(line)s' % \
+                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' % \
                           {'desc': desc,
-                           'pr_id': comment.pull_request.pull_request_id,
+                           'pr_nice_id': comment.pull_request.nice_id(),
                            'line': line},
                           comment_url)
             )
@@ -148,7 +144,7 @@
             #set some variables for email notification
             email_kwargs = {
                 'pr_title': pull_request.title,
-                'pr_id': pull_request.pull_request_id,
+                'pr_nice_id': pull_request.nice_id(),
                 'status_change': status_change,
                 'closing_pr': closing_pr,
                 'pr_comment_url': comment_url,
@@ -167,22 +163,12 @@
                f_path=None, line_no=None, status_change=None, closing_pr=False,
                send_email=True):
         """
-        Creates new comment for changeset or pull request.
-        If status_change is not None this comment is associated with a
-        status change of changeset or changesets associated with pull request
+        Creates a new comment for either a changeset or a pull request.
+        status_change and closing_pr is only for the optional email.
 
-        :param text:
-        :param repo:
-        :param user:
-        :param revision:
-        :param pull_request: (for emails, not for comments)
-        :param f_path:
-        :param line_no:
-        :param status_change: (for emails, not for comments)
-        :param closing_pr: (for emails, not for comments)
-        :param send_email: also send email
+        Returns the created comment.
         """
-        if not text:
+        if not status_change and not text:
             log.warning('Missing text for comment, skipping...')
             return
 
@@ -239,11 +225,6 @@
         return comment
 
     def delete(self, comment):
-        """
-        Deletes given comment
-
-        :param comment_id:
-        """
         comment = self.__get_changeset_comment(comment)
         Session().delete(comment)
 
@@ -251,33 +232,40 @@
 
     def get_comments(self, repo_id, revision=None, pull_request=None):
         """
-        Gets main comments based on revision or pull_request_id
-
-        :param repo_id:
-        :param revision:
-        :param pull_request:
-        """
+        Gets general comments for either revision or pull_request.
 
-        q = ChangesetComment.query()\
-                .filter(ChangesetComment.repo_id == repo_id)\
-                .filter(ChangesetComment.line_no == None)\
-                .filter(ChangesetComment.f_path == None)
-        if revision:
-            q = q.filter(ChangesetComment.revision == revision)
-        elif pull_request:
-            pull_request = self.__get_pull_request(pull_request)
-            q = q.filter(ChangesetComment.pull_request == pull_request)
-        else:
-            raise Exception('Please specify revision or pull_request')
-        q = q.order_by(ChangesetComment.created_on)
-        return q.all()
+        Returns a list, ordered by creation date.
+        """
+        return self._get_comments(repo_id, revision=revision, pull_request=pull_request,
+                                  inline=False)
 
     def get_inline_comments(self, repo_id, revision=None, pull_request=None):
+        """
+        Gets inline comments for either revision or pull_request.
+
+        Returns a list of tuples with file path and list of comments per line number.
+        """
+        comments = self._get_comments(repo_id, revision=revision, pull_request=pull_request,
+                                      inline=True)
+
+        paths = defaultdict(lambda: defaultdict(list))
+        for co in comments:
+            paths[co.f_path][co.line_no].append(co)
+        return paths.items()
+
+    def _get_comments(self, repo_id, revision=None, pull_request=None, inline=False):
+        """
+        Gets comments for either revision or pull_request_id, either inline or general.
+        """
         q = Session().query(ChangesetComment)\
-            .filter(ChangesetComment.repo_id == repo_id)\
-            .filter(ChangesetComment.line_no != None)\
-            .filter(ChangesetComment.f_path != None)\
-            .order_by(ChangesetComment.comment_id.asc())\
+            .filter(ChangesetComment.repo_id == repo_id)
+
+        if inline:
+            q = q.filter(ChangesetComment.line_no != None)\
+                .filter(ChangesetComment.f_path != None)
+        else:
+            q = q.filter(ChangesetComment.line_no == None)\
+                .filter(ChangesetComment.f_path == None)
 
         if revision:
             q = q.filter(ChangesetComment.revision == revision)
@@ -285,12 +273,6 @@
             pull_request = self.__get_pull_request(pull_request)
             q = q.filter(ChangesetComment.pull_request == pull_request)
         else:
-            raise Exception('Please specify revision or pull_request_id')
-
-        comments = q.all()
+            raise Exception('Please specify either revision or pull_request')
 
-        paths = defaultdict(lambda: defaultdict(list))
-
-        for co in comments:
-            paths[co.f_path][co.line_no].append(co)
-        return paths.items()
+        return q.order_by(ChangesetComment.created_on).all()
--- a/kallithea/model/db.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/model/db.py	Thu May 07 13:30:17 2015 +0200
@@ -175,8 +175,8 @@
     }
     DEFAULT_UPDATE_URL = ''
 
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    app_settings_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    app_settings_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
     _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
@@ -338,11 +338,11 @@
     HOOK_PULL = 'outgoing.pull_logger'
     HOOK_PRE_PULL = 'preoutgoing.pre_pull'
 
-    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+    ui_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_key = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_value = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    ui_active = Column(Boolean(), nullable=True, unique=None, default=True)
 
     # def __init__(self, section='', key='', value=''):
     #     self.ui_section = section
@@ -401,20 +401,20 @@
     DEFAULT_USER = 'default'
     DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
 
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    user_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    password = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    active = Column(Boolean(), nullable=True, unique=None, default=True)
+    admin = Column(Boolean(), nullable=True, unique=None, default=False)
     name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    lastname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
     _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    last_login = Column(DateTime(timezone=False), nullable=True, unique=None, default=None)
+    extern_type = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    extern_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    api_key = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    inherit_default_permissions = Column(Boolean(), nullable=False, unique=None, default=True)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
 
     user_log = relationship('UserLog')
@@ -675,12 +675,12 @@
     )
     __mapper_args__ = {}
 
-    user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
-    description = Column('description', UnicodeText(1024))
-    expires = Column('expires', Float(53), nullable=False)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_api_key_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    api_key = Column(String(255, convert_unicode=False), nullable=False, unique=True)
+    description = Column(UnicodeText(1024))
+    expires = Column(Float(53), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     user = relationship('User')
 
@@ -701,8 +701,8 @@
     )
     __mapper_args__ = {}
 
-    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    email_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
     _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     user = relationship('User')
 
@@ -732,10 +732,10 @@
     )
     __mapper_args__ = {}
 
-    ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+    ip_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    ip_addr = Column(String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    active = Column(Boolean(), nullable=True, unique=None, default=True)
     user = relationship('User')
 
     @classmethod
@@ -760,14 +760,14 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
-    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
-    repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    user_log_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    user_ip = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    action = Column(UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
+    action_date = Column(DateTime(timezone=False), nullable=True, unique=None, default=None)
 
     def __unicode__(self):
         return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
@@ -789,13 +789,13 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
-    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+    users_group_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    user_group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    users_group_active = Column(Boolean(), nullable=True, unique=None, default=None)
     inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
 
     members = relationship('UserGroupMember', cascade="all, delete-orphan")
@@ -879,9 +879,9 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
 
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    users_group_member_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     users_group = relationship('UserGroup')
@@ -900,14 +900,14 @@
     )
     PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
 
-    repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-    field_key = Column("field_key", String(250, convert_unicode=False))
-    field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
-    field_type = Column("field_type", String(255), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    repo_field_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    field_key = Column(String(250, convert_unicode=False))
+    field_label = Column(String(1024, convert_unicode=False), nullable=False)
+    field_value = Column(String(10000, convert_unicode=False), nullable=False)
+    field_desc = Column(String(1024, convert_unicode=False), nullable=False)
+    field_type = Column(String(255), nullable=False, unique=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     repository = relationship('Repository')
 
@@ -944,26 +944,26 @@
     STATE_PENDING = 'repo_state_pending'
     STATE_ERROR = 'repo_state_error'
 
-    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    repo_state = Column("repo_state", String(255), nullable=True)
-
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    repo_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repo_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    repo_state = Column(String(255), nullable=True)
+
+    clone_uri = Column(String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    repo_type = Column(String(255, convert_unicode=False), nullable=False, unique=False, default=None)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column(Boolean(), nullable=True, unique=None, default=None)
     enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
     enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    created_on = Column(DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    updated_on = Column(DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
     _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+    enable_locking = Column(Boolean(), nullable=False, unique=None, default=False)
     _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
     _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
 
-    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+    fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
 
     user = relationship('User')
     fork = relationship('Repository', remote_side=repo_id)
@@ -1277,7 +1277,7 @@
                 pass
 
         return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualifed_home_url=qualified_home_url,
+                             qualified_home_url=qualified_home_url,
                              repo_name=self.repo_name,
                              repo_id=self.repo_id, **override)
 
@@ -1384,12 +1384,13 @@
 
         grouped = {}
         for stat in statuses.all():
-            pr_id = pr_repo = None
+            pr_id = pr_nice_id = pr_repo = None
             if stat.pull_request:
                 pr_id = stat.pull_request.pull_request_id
+                pr_nice_id = PullRequest.make_nice_id(pr_id)
                 pr_repo = stat.pull_request.other_repo.repo_name
             grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
+                                      pr_id, pr_repo, pr_nice_id]
         return grouped
 
     def _repo_size(self):
@@ -1463,13 +1464,13 @@
 
     SEP = ' &raquo; '
 
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    group_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
+    group_parent_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+    group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
+    enable_locking = Column(Boolean(), nullable=False, unique=None, default=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
     users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
@@ -1728,9 +1729,9 @@
         'hg.create.repository': 1
     }
 
-    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_longname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (
@@ -1776,10 +1777,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    repo_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     repository = relationship('Repository')
@@ -1805,10 +1806,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     user_group = relationship('UserGroup')
@@ -1834,9 +1835,9 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     permission = relationship('Permission')
@@ -1852,10 +1853,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1882,10 +1883,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    user_group_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
     target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
     user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
@@ -1911,9 +1912,9 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1927,10 +1928,10 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
-    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    group_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     user = relationship('User')
     group = relationship('RepoGroup')
@@ -1954,10 +1955,10 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    users_group_repo_group_to_perm_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1980,12 +1981,12 @@
          {'extend_existing': True, 'mysql_engine': 'InnoDB',
           'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+    stat_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+    stat_on_revision = Column(Integer(), nullable=False)
+    commit_activity = Column(LargeBinary(1000000), nullable=False)#JSON data
+    commit_activity_combined = Column(LargeBinary(), nullable=False)#JSON data
+    languages = Column(LargeBinary(1000000), nullable=False)#JSON data
 
     repository = relationship('Repository', single_parent=True)
 
@@ -1999,11 +2000,11 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
 
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    user_following_id = Column(Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
     follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+    follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+    follows_from = Column(DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
 
     user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
 
@@ -2024,14 +2025,14 @@
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
     # cache_id, not used
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    cache_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
     # cache_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    cache_key = Column(String(255, convert_unicode=False))
     # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    # instance sets cache_active True when it is caching,
-    # other instances set cache_active to False to indicate that this cache is invalid
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+    cache_args = Column(String(255, convert_unicode=False))
+    # instance sets cache_active True when it is caching, other instances set
+    # cache_active to False to indicate that this cache is invalid
+    cache_active = Column(Boolean(), nullable=True, unique=None, default=False)
 
     def __init__(self, cache_key, repo_name=''):
         self.cache_key = cache_key
@@ -2039,8 +2040,9 @@
         self.cache_active = False
 
     def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
-                            self.cache_id, self.cache_key, self.cache_active)
+        return u"<%s('%s:%s[%s]')>" % (
+            self.__class__.__name__,
+            self.cache_id, self.cache_key, self.cache_active)
 
     def _cache_key_partition(self):
         prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
@@ -2143,24 +2145,25 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
-    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
-    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    revision = Column('revision', String(40), nullable=True)
-    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
-    line_no = Column('line_no', Unicode(10), nullable=True)
-    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
-    f_path = Column('f_path', Unicode(1000), nullable=True)
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
-    text = Column('text', UnicodeText(25000), nullable=False)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    comment_id = Column(Integer(), nullable=False, primary_key=True)
+    repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    revision = Column(String(40))
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'))
+    line_no = Column(Unicode(10))
+    hl_lines = Column(Unicode(512))
+    f_path = Column(Unicode(1000))
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column(UnicodeText(25000), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     author = relationship('User')
     repo = relationship('Repository')
     # status_change is frequently used directly in templates - make it a lazy
     # join to avoid fetching each related ChangesetStatus on demand.
     # There will only be one ChangesetStatus referencing each comment so the join will not explode.
-    status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
+    status_change = relationship('ChangesetStatus',
+                                 cascade="all, delete-orphan", lazy='joined')
     pull_request = relationship('PullRequest')
 
     @classmethod
@@ -2205,15 +2208,15 @@
         (STATUS_UNDER_REVIEW, _("Under Review")),
     ]
 
-    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
-    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
-    revision = Column('revision', String(40), nullable=False)
-    status = Column('status', String(128), nullable=False, default=DEFAULT)
-    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
-    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
-    version = Column('version', Integer(), nullable=False, default=0)
-    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    changeset_status_id = Column(Integer(), nullable=False, primary_key=True)
+    repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    revision = Column(String(40), nullable=False)
+    status = Column(String(128), nullable=False, default=DEFAULT)
+    changeset_comment_id = Column(Integer(), ForeignKey('changeset_comments.comment_id'))
+    modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
+    version = Column(Integer(), nullable=False, default=0)
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
 
     author = relationship('User')
     repo = relationship('Repository')
@@ -2248,18 +2251,18 @@
     STATUS_NEW = u'new'
     STATUS_CLOSED = u'closed'
 
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(255), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+    pull_request_id = Column(Integer(), primary_key=True)
+    title = Column(Unicode(255), nullable=True)
+    description = Column(UnicodeText(10240))
+    status = Column(Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
     _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
-    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    org_ref = Column('org_ref', Unicode(255), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(255), nullable=False)
+    org_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    org_ref = Column(Unicode(255), nullable=False)
+    other_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column(Unicode(255), nullable=False)
 
     @hybrid_property
     def revisions(self):
@@ -2303,6 +2306,15 @@
             .first()
         return str(status.status) if status else ''
 
+    @classmethod
+    def make_nice_id(cls, pull_request_id):
+        '''Return pull request id nicely formatted for displaying'''
+        return '#%s' % pull_request_id
+
+    def nice_id(self):
+        '''Return the id of this pull request, nicely formatted for displaying'''
+        return self.make_nice_id(self.pull_request_id)
+
     def __json__(self):
         return dict(
             revisions=self.revisions
@@ -2334,9 +2346,9 @@
         self.user = user
         self.pull_request = pull_request
 
-    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
-    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+    pull_requests_reviewers_id = Column(Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
 
     user = relationship('User')
     pull_request = relationship('PullRequest')
@@ -2357,11 +2369,11 @@
     TYPE_PULL_REQUEST = u'pull_request'
     TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
 
-    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
-    subject = Column('subject', Unicode(512), nullable=True)
-    body = Column('body', UnicodeText(50000), nullable=True)
-    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    notification_id = Column(Integer(), nullable=False, primary_key=True)
+    subject = Column(Unicode(512), nullable=True)
+    body = Column(UnicodeText(50000), nullable=True)
+    created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     type_ = Column('type', Unicode(255))
 
     created_by_user = relationship('User')
@@ -2369,8 +2381,8 @@
 
     @property
     def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
+        return [x.user for x in UserNotification.query()
+                .filter(UserNotification.notification == self)
                 .order_by(UserNotification.user_id.asc()).all()]
 
     @classmethod
@@ -2407,10 +2419,10 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
     )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column(Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+    read = Column(Boolean, default=False)
+    sent_on = Column(DateTime(timezone=False), nullable=True, unique=None)
 
     user = relationship('User')
     notification = relationship('Notification')
@@ -2432,14 +2444,14 @@
     GIST_PRIVATE = u'private'
     DEFAULT_FILENAME = u'gistfile1.txt'
 
-    gist_id = Column('gist_id', Integer(), primary_key=True)
-    gist_access_id = Column('gist_access_id', Unicode(250))
-    gist_description = Column('gist_description', UnicodeText(1024))
+    gist_id = Column(Integer(), primary_key=True)
+    gist_access_id = Column(Unicode(250))
+    gist_description = Column(UnicodeText(1024))
     gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
-    gist_expires = Column('gist_expires', Float(53), nullable=False)
-    gist_type = Column('gist_type', Unicode(128), nullable=False)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    gist_expires = Column(Float(53), nullable=False)
+    gist_type = Column(Unicode(128), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     owner = relationship('User')
 
@@ -2516,6 +2528,6 @@
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
          'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
     )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
+    repository_id = Column(String(250), primary_key=True)
+    repository_path = Column(Text)
+    version = Column(Integer)
--- a/kallithea/model/notification.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/model/notification.py	Thu May 07 13:30:17 2015 +0200
@@ -298,8 +298,8 @@
             # self.TYPE_PASSWORD_RESET
             self.TYPE_REGISTRATION: _('New user %(new_username)s registered'),
             # self.TYPE_DEFAULT
-            self.TYPE_PULL_REQUEST: _('Review request on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(pr_username)s'),
-            self.TYPE_PULL_REQUEST_COMMENT: _('Comment on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(comment_username)s'),
+            self.TYPE_PULL_REQUEST: _('Review request on %(repo_name)s pull request %(pr_nice_id)s from %(ref)s by %(pr_username)s'),
+            self.TYPE_PULL_REQUEST_COMMENT: _('Comment on %(repo_name)s pull request %(pr_nice_id)s from %(ref)s by %(comment_username)s'),
         }
 
     def get_email_description(self, type_, **kwargs):
--- a/kallithea/model/pull_request.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/model/pull_request.py	Thu May 07 13:30:17 2015 +0200
@@ -129,10 +129,10 @@
                                      pull_request_id=pr.pull_request_id)]
         subject = safe_unicode(
             h.link_to(
-              _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
+              _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \
                 {'user': pr.author.username,
                  'pr_title': pr.title,
-                 'pr_id': pr.pull_request_id},
+                 'pr_nice_id': pr.nice_id()},
                 pr_url)
             )
         body = pr.description
@@ -144,7 +144,7 @@
             'pr_url': pr_url,
             'pr_revisions': revision_data,
             'repo_name': pr.other_repo.repo_name,
-            'pr_id': pr.pull_request_id,
+            'pr_nice_id': pr.nice_id(),
             'ref': org_ref_name,
             'pr_username': pr.author.username,
             'threading': threading,
--- a/kallithea/model/user.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/model/user.py	Thu May 07 13:30:17 2015 +0200
@@ -81,12 +81,17 @@
         if not cur_user:
             cur_user = getattr(get_current_authuser(), 'username', None)
 
-        from kallithea.lib.hooks import log_create_user, check_allowed_create_user
+        from kallithea.lib.hooks import log_create_user, \
+            check_allowed_create_user
         _fd = form_data
         user_data = {
-            'username': _fd['username'], 'password': _fd['password'],
-            'email': _fd['email'], 'firstname': _fd['firstname'], 'lastname': _fd['lastname'],
-            'active': _fd['active'], 'admin': False
+            'username': _fd['username'],
+            'password': _fd['password'],
+            'email': _fd['email'],
+            'firstname': _fd['firstname'],
+            'lastname': _fd['lastname'],
+            'active': _fd['active'],
+            'admin': False
         }
         # raises UserCreationError if it's not allowed
         check_allowed_create_user(user_data, cur_user)
@@ -128,7 +133,8 @@
             cur_user = getattr(get_current_authuser(), 'username', None)
 
         from kallithea.lib.auth import get_crypt_password, check_password
-        from kallithea.lib.hooks import log_create_user, check_allowed_create_user
+        from kallithea.lib.hooks import log_create_user, \
+            check_allowed_create_user
         user_data = {
             'username': username, 'password': password,
             'email': email, 'firstname': firstname, 'lastname': lastname,
@@ -153,8 +159,10 @@
             new_user.admin = admin
             new_user.email = email
             new_user.active = active
-            new_user.extern_name = safe_unicode(extern_name) if extern_name else None
-            new_user.extern_type = safe_unicode(extern_type) if extern_type else None
+            new_user.extern_name = safe_unicode(extern_name) \
+                if extern_name else None
+            new_user.extern_type = safe_unicode(extern_type) \
+                if extern_type else None
             new_user.name = firstname
             new_user.lastname = lastname
 
@@ -162,12 +170,13 @@
                 new_user.api_key = generate_api_key(username)
 
             # set password only if creating an user or password is changed
-            password_change = new_user.password and not check_password(password,
-                                                            new_user.password)
+            password_change = new_user.password and \
+                not check_password(password, new_user.password)
             if not edit or password_change:
                 reason = 'new password' if edit else 'new user'
                 log.debug('Updating password reason=>%s' % (reason,))
-                new_user.password = get_crypt_password(password) if password else None
+                new_user.password = get_crypt_password(password) \
+                    if password else None
 
             self.sa.add(new_user)
 
@@ -192,14 +201,17 @@
 
         # notification to admins
         subject = _('New user registration')
-        body = ('New user registration\n'
-                '---------------------\n'
-                '- Username: %s\n'
-                '- Full Name: %s\n'
-                '- Email: %s\n')
-        body = body % (new_user.username, new_user.full_name, new_user.email)
+        body = (
+            'New user registration\n'
+            '---------------------\n'
+            '- Username: {user.username}\n'
+            '- Full Name: {user.full_name}\n'
+            '- Email: {user.email}\n'
+            ).format(user=new_user)
         edit_url = h.canonical_url('edit_user', id=new_user.user_id)
-        email_kwargs = {'registered_user_url': edit_url, 'new_username': new_user.username}
+        email_kwargs = {
+            'registered_user_url': edit_url,
+            'new_username': new_user.username}
         NotificationModel().create(created_by=new_user, subject=subject,
                                    body=body, recipients=None,
                                    type_=Notification.TYPE_REGISTRATION,
@@ -253,29 +265,25 @@
         if user.username == User.DEFAULT_USER:
             raise DefaultUserException(
                 _(u"You can't remove this user since it's"
-                  " crucial for entire application")
-            )
+                  " crucial for entire application"))
         if user.repositories:
             repos = [x.repo_name for x in user.repositories]
             raise UserOwnsReposException(
                 _(u'User "%s" still owns %s repositories and cannot be '
                   'removed. Switch owners or remove those repositories: %s')
-                % (user.username, len(repos), ', '.join(repos))
-            )
+                % (user.username, len(repos), ', '.join(repos)))
         if user.repo_groups:
             repogroups = [x.group_name for x in user.repo_groups]
-            raise UserOwnsReposException(
-                _(u'User "%s" still owns %s repository groups and cannot be '
-                  'removed. Switch owners or remove those repository groups: %s')
-                % (user.username, len(repogroups), ', '.join(repogroups))
-            )
+            raise UserOwnsReposException(_(
+                'User "%s" still owns %s repository groups and cannot be '
+                'removed. Switch owners or remove those repository groups: %s')
+                % (user.username, len(repogroups), ', '.join(repogroups)))
         if user.user_groups:
             usergroups = [x.users_group_name for x in user.user_groups]
             raise UserOwnsReposException(
-                _(u'User "%s" still owns %s user groups and cannot be '
+                _('User "%s" still owns %s user groups and cannot be '
                   'removed. Switch owners or remove those user groups: %s')
-                % (user.username, len(usergroups), ', '.join(usergroups))
-            )
+                % (user.username, len(usergroups), ', '.join(usergroups)))
         self.sa.delete(user)
 
         from kallithea.lib.hooks import log_delete_user
@@ -290,16 +298,17 @@
         user = User.get_by_email(user_email)
         if user:
             log.debug('password reset user found %s' % user)
-            link = h.canonical_url('reset_password_confirmation', key=user.api_key)
+            link = h.canonical_url('reset_password_confirmation',
+                                   key=user.api_key)
             reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
-            body = EmailNotificationModel().get_email_tmpl(reg_type,
-                                                           'txt',
-                                                           user=user.short_contact,
-                                                           reset_url=link)
-            html_body = EmailNotificationModel().get_email_tmpl(reg_type,
-                                                           'html',
-                                                           user=user.short_contact,
-                                                           reset_url=link)
+            body = EmailNotificationModel().get_email_tmpl(
+                reg_type, 'txt',
+                user=user.short_contact,
+                reset_url=link)
+            html_body = EmailNotificationModel().get_email_tmpl(
+                reg_type, 'html',
+                user=user.short_contact,
+                reset_url=link)
             log.debug('sending email')
             run_task(tasks.send_email, [user_email],
                      _("Password reset link"), body, html_body)
@@ -314,8 +323,8 @@
         from kallithea.lib import auth
         user_email = data['email']
         user = User.get_by_email(user_email)
-        new_passwd = auth.PasswordGenerator().gen_password(8,
-                        auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
+        new_passwd = auth.PasswordGenerator().gen_password(
+            8, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
         if user:
             user.password = auth.get_crypt_password(new_passwd)
             Session().add(user)
@@ -401,12 +410,10 @@
         user = self._get_user(user)
         perm = self._get_perm(perm)
 
-        obj = UserToPerm.query()\
-                .filter(UserToPerm.user == user)\
-                .filter(UserToPerm.permission == perm)\
-                .scalar()
-        if obj:
-            self.sa.delete(obj)
+        UserToPerm.query().filter(
+            UserToPerm.user == user,
+            UserToPerm.permission == perm,
+        ).delete()
 
     def add_extra_email(self, user, email):
         """
--- a/kallithea/public/css/contextbar.css	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/public/css/contextbar.css	Thu May 07 13:30:17 2015 +0200
@@ -19,19 +19,19 @@
 
 .icon-diff-M:before {
     font-family: 'kallithea';
-    content: '\e805';
+    content: '\22a1';
     color: #d0b44c;
 }
 
 .icon-diff-D:before {
     font-family: 'kallithea';
-    content: '\e807';
+    content: '\229f';
     color: #bd2c00;
 }
 
 .icon-diff-A:before {
     font-family: 'kallithea';
-    content: '\e806';
+    content: '\229e';
     color: #6cc644;
 }
 
@@ -279,7 +279,8 @@
 #revision-changer:before,
 #context-pages a.childs:after,
 #context-pages a.dropdown:after {
-    content: ' \25BE';
+    font-family: 'kallithea';
+    content: ' \23f7';
 }
 #context-pages a.childs {
     padding-right: 30px;
--- a/kallithea/public/css/style.css	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/public/css/style.css	Thu May 07 13:30:17 2015 +0200
@@ -510,7 +510,7 @@
 
 td.quick_repo_menu:before {
     font-family: "kallithea";
-    content: "\e80f";           /* triangle-right */
+    content: "\23f5";           /* triangle-right */
     margin-left: 3px;
     padding-right: 3px;
 }
@@ -523,7 +523,7 @@
 
 td.quick_repo_menu.active:before {
     font-family: "kallithea";
-    content: "\e80d";           /* triangle-down */
+    content: "\23f7";           /* triangle-down */
     margin-left: 1px;
     padding-right: 1px;
 }
@@ -735,12 +735,12 @@
 
 .yui-skin-sam th.yui-dt-asc .yui-dt-liner:after {
     font-family: "kallithea";
-    content: "\e810";           /* triangle-up */
+    content: "\23f6";           /* triangle-up */
 }
 
 .yui-skin-sam th.yui-dt-desc .yui-dt-liner:after {
     font-family: "kallithea";
-    content: "\e80d";           /* triangle-down */
+    content: "\23f7";           /* triangle-down */
 }
 
 tbody .yui-dt-editable { cursor: pointer }
@@ -3715,7 +3715,8 @@
 }
 
 .repo-switcher .select2-chosen:after {
-    content: ' \25BE';
+    font-family: 'kallithea';
+    content: ' \23f7';
 }
 
 .repo-switcher-dropdown.select2-drop.select2-drop-active {
@@ -4346,6 +4347,10 @@
     font-size: 16px;
 }
 
+.automatic-comment {
+    font-style: italic;
+}
+
 /** comment form **/
 
 .status-block {
@@ -4968,7 +4973,7 @@
     position: relative;
     width: 0;
 }
- 
+
 table.code-difftable .add .code pre:before {
     content: "+";
     color: #080;
@@ -4977,7 +4982,7 @@
     position: relative;
     width: 0;
 }
- 
+
 table.code-difftable .unmod .code pre:before {
     content: " ";
     float: left;
@@ -4985,7 +4990,7 @@
     position: relative;
     width: 0;
 }
- 
+
 .add-bubble {
     position: relative;
     display: none;
@@ -5022,7 +5027,7 @@
     font-size: 14px;
     color: #ffffff;
     font-family: "kallithea";
-    content: '\e80c';
+    content: '\1f5ea';
 }
 
 .add-bubble div:hover {
--- a/kallithea/public/fontello/config.json	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/public/fontello/config.json	Thu May 07 13:30:17 2015 +0200
@@ -401,7 +401,7 @@
     {
       "uid": "c04672812513206682a859166c207252",
       "css": "clippy",
-      "code": 59419,
+      "code": 128203,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -457,7 +457,7 @@
     {
       "uid": "c9e14800494dc6027b3dcee0817cf984",
       "css": "code",
-      "code": 59425,
+      "code": 8596,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -485,7 +485,7 @@
     {
       "uid": "7aeda197b5b0fba630669530807d736d",
       "css": "comment",
-      "code": 59403,
+      "code": 128489,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -499,7 +499,7 @@
     {
       "uid": "28655242f029e97efd1bf5379e22c4ba",
       "css": "comment-discussion",
-      "code": 59404,
+      "code": 128490,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -555,7 +555,7 @@
     {
       "uid": "776af96a4db73c51c13fd256f12d6f40",
       "css": "database",
-      "code": 59392,
+      "code": 9923,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -639,7 +639,7 @@
     {
       "uid": "ccc8f787411138446b85c9fe52e33b8b",
       "css": "diff-added",
-      "code": 59398,
+      "code": 8862,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -667,7 +667,7 @@
     {
       "uid": "2672a84540d3632649f01108e2db0341",
       "css": "diff-modified",
-      "code": 59397,
+      "code": 8865,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -681,7 +681,7 @@
     {
       "uid": "1ce39df7ee1648c4054633b371546367",
       "css": "diff-removed",
-      "code": 59399,
+      "code": 8863,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -905,7 +905,7 @@
     {
       "uid": "c59600c892bd7e46476f38e89812fbbf",
       "css": "gear",
-      "code": 59441,
+      "code": 9965,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1031,7 +1031,7 @@
     {
       "uid": "b00e349d8883c0b89796940a9913e8e8",
       "css": "globe",
-      "code": 59410,
+      "code": 127758,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1283,7 +1283,7 @@
     {
       "uid": "3617d121a15ef91892d5083aeead6ce3",
       "css": "key",
-      "code": 59430,
+      "code": 128273,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1591,7 +1591,7 @@
     {
       "uid": "ef6263a57a35ecee2f8ddda2fe20dcab",
       "css": "move-down",
-      "code": 59395,
+      "code": 11123,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -1633,7 +1633,7 @@
     {
       "uid": "13e7ebf511552da48a5bc1d2e7cf6de2",
       "css": "move-up",
-      "code": 59396,
+      "code": 11121,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2123,7 +2123,7 @@
     {
       "uid": "809bb7ea7bf6ca7e94029eae402f922f",
       "css": "search",
-      "code": 59435,
+      "code": 128269,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2333,7 +2333,7 @@
     {
       "uid": "92dd16866a2367537f47bec5ee11484f",
       "css": "tools",
-      "code": 59400,
+      "code": 128736,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2347,7 +2347,7 @@
     {
       "uid": "448bfec39235117b6a7e3e8d46aa184f",
       "css": "trashcan",
-      "code": 59433,
+      "code": 128465,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2361,7 +2361,7 @@
     {
       "uid": "ebbb1978bedc8e562ab0d462a832919f",
       "css": "triangle-down",
-      "code": 59405,
+      "code": 9207,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2375,7 +2375,7 @@
     {
       "uid": "814cb1fe473de29d20b3d4e366ed434b",
       "css": "triangle-left",
-      "code": 59406,
+      "code": 9204,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2389,7 +2389,7 @@
     {
       "uid": "d191392505c6a27bbeafd2d283a18d23",
       "css": "triangle-right",
-      "code": 59407,
+      "code": 9205,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2403,7 +2403,7 @@
     {
       "uid": "14bdc9ddaebbaecf5bad552a14cae637",
       "css": "triangle-up",
-      "code": 59408,
+      "code": 9206,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2501,7 +2501,7 @@
     {
       "uid": "16b70cc2a5fbff7de2cbf73498e25b5c",
       "css": "keyhole-circled",
-      "code": 59426,
+      "code": 128272,
       "src": "custom_icons",
       "selected": true,
       "svg": {
@@ -2543,91 +2543,91 @@
     {
       "uid": "d73eceadda1f594cec0536087539afbf",
       "css": "heart",
-      "code": 59436,
+      "code": 9829,
       "src": "fontawesome"
     },
     {
       "uid": "f3dc2d6d8fe9cf9ebff84dc260888cdf",
       "css": "heart-empty",
-      "code": 59437,
+      "code": 9825,
       "src": "fontawesome"
     },
     {
       "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
       "css": "user",
-      "code": 59438,
+      "code": 128100,
       "src": "fontawesome"
     },
     {
       "uid": "31972e4e9d080eaa796290349ae6c1fd",
       "css": "users",
-      "code": 59439,
+      "code": 128101,
       "src": "fontawesome"
     },
     {
       "uid": "12f4ece88e46abd864e40b35e05b11cd",
       "css": "ok",
-      "code": 59440,
+      "code": 128504,
       "src": "fontawesome"
     },
     {
       "uid": "43ab845088317bd348dee1d975700c48",
       "css": "ok-circled",
-      "code": 59402,
+      "code": 128505,
       "src": "fontawesome"
     },
     {
       "uid": "5211af474d3a9848f67f945e2ccaf143",
       "css": "cancel",
-      "code": 59434,
+      "code": 128500,
       "src": "fontawesome"
     },
     {
       "uid": "0f4cae16f34ae243a6144c18a003f2d8",
       "css": "cancel-circled",
-      "code": 59401,
+      "code": 128501,
       "src": "fontawesome"
     },
     {
       "uid": "44e04715aecbca7f266a17d5a7863c68",
       "css": "plus",
-      "code": 59471,
+      "code": 10133,
       "src": "fontawesome"
     },
     {
       "uid": "4ba33d2607902cf690dd45df09774cb0",
       "css": "plus-circled",
-      "code": 59468,
+      "code": 8853,
       "src": "fontawesome"
     },
     {
       "uid": "861ab06e455e2de3232ebef67d60d708",
       "css": "minus",
-      "code": 59472,
+      "code": 10134,
       "src": "fontawesome"
     },
     {
       "uid": "eeadb020bb75d089b25d8424aabe19e0",
       "css": "minus-circled",
-      "code": 59469,
+      "code": 8854,
       "src": "fontawesome"
     },
     {
       "uid": "c1f1975c885aa9f3dad7810c53b82074",
       "css": "lock",
-      "code": 59450,
+      "code": 128274,
       "src": "fontawesome"
     },
     {
       "uid": "05376be04a27d5a46e855a233d6e8508",
       "css": "lock-open-alt",
-      "code": 59451,
+      "code": 128275,
       "src": "fontawesome"
     },
     {
       "uid": "c5fd349cbd3d23e4ade333789c29c729",
       "css": "eye",
-      "code": 59474,
+      "code": 128065,
       "src": "fontawesome"
     },
     {
@@ -2639,7 +2639,7 @@
     {
       "uid": "3db5347bd219f3bce6025780f5d9ef45",
       "css": "tag",
-      "code": 59452,
+      "code": 128278,
       "src": "fontawesome"
     },
     {
@@ -2675,25 +2675,25 @@
     {
       "uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
       "css": "pencil",
-      "code": 59458,
+      "code": 128393,
       "src": "fontawesome"
     },
     {
       "uid": "44fae3bfdd54754dc68ec50d37efea37",
       "css": "pencil-squared",
-      "code": 59460,
+      "code": 128394,
       "src": "fontawesome"
     },
     {
       "uid": "41087bc74d4b20b55059c60a33bf4008",
       "css": "edit",
-      "code": 59461,
+      "code": 128395,
       "src": "fontawesome"
     },
     {
       "uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
       "css": "doc",
-      "code": 59443,
+      "code": 128453,
       "src": "fontawesome"
     },
     {
@@ -2705,7 +2705,7 @@
     {
       "uid": "5408be43f7c42bccee419c6be53fdef5",
       "css": "doc-text",
-      "code": 59445,
+      "code": 128456,
       "src": "fontawesome"
     },
     {
@@ -2729,25 +2729,25 @@
     {
       "uid": "f8aa663c489bcbd6e68ec8147dca841e",
       "css": "folder",
-      "code": 59475,
+      "code": 128448,
       "src": "fontawesome"
     },
     {
       "uid": "c95735c17a10af81448c7fed98a04546",
       "css": "folder-open",
-      "code": 59462,
+      "code": 128449,
       "src": "fontawesome"
     },
     {
       "uid": "b091a8bd0fdade174951f17d936f51e4",
       "css": "folder-empty",
-      "code": 59476,
+      "code": 128193,
       "src": "fontawesome"
     },
     {
       "uid": "6533bdc16ab201eb3f3b27ce989cab33",
       "css": "folder-open-empty",
-      "code": 59463,
+      "code": 128194,
       "src": "fontawesome"
     },
     {
@@ -2771,13 +2771,13 @@
     {
       "uid": "98687378abd1faf8f6af97c254eb6cd6",
       "css": "cog-alt",
-      "code": 59442,
+      "code": 9881,
       "src": "fontawesome"
     },
     {
       "uid": "5bb103cd29de77e0e06a52638527b575",
       "css": "wrench",
-      "code": 59479,
+      "code": 128295,
       "src": "fontawesome"
     },
     {
@@ -2789,19 +2789,19 @@
     {
       "uid": "598a5f2bcf3521d1615de8e1881ccd17",
       "css": "clock",
-      "code": 59449,
+      "code": 8986,
       "src": "fontawesome"
     },
     {
       "uid": "98d9c83c1ee7c2c25af784b518c522c5",
       "css": "block",
-      "code": 59470,
+      "code": 128683,
       "src": "fontawesome"
     },
     {
       "uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
       "css": "resize-vertical",
-      "code": 59415,
+      "code": 11109,
       "src": "fontawesome"
     },
     {
@@ -2825,25 +2825,25 @@
     {
       "uid": "ccddff8e8670dcd130e3cb55fdfc2fd0",
       "css": "down-open",
-      "code": 59464,
+      "code": 8595,
       "src": "fontawesome"
     },
     {
       "uid": "d870630ff8f81e6de3958ecaeac532f2",
       "css": "left-open",
-      "code": 59465,
+      "code": 8594,
       "src": "fontawesome"
     },
     {
       "uid": "399ef63b1e23ab1b761dfbb5591fa4da",
       "css": "right-open",
-      "code": 59466,
+      "code": 8592,
       "src": "fontawesome"
     },
     {
       "uid": "fe6697b391355dec12f3d86d6d490397",
       "css": "up-open",
-      "code": 59467,
+      "code": 8593,
       "src": "fontawesome"
     },
     {
@@ -2879,13 +2879,13 @@
     {
       "uid": "a73c5deb486c8d66249811642e5d719a",
       "css": "arrows-cw",
-      "code": 59448,
+      "code": 128472,
       "src": "fontawesome"
     },
     {
       "uid": "6020aff067fc3c119cdd75daa5249220",
       "css": "exchange",
-      "code": 59483,
+      "code": 8644,
       "src": "fontawesome"
     },
     {
@@ -2909,25 +2909,25 @@
     {
       "uid": "9755f76110ae4d12ac5f9466c9152031",
       "css": "book",
-      "code": 59454,
+      "code": 128210,
       "src": "fontawesome"
     },
     {
       "uid": "130380e481a7defc690dfb24123a1f0c",
       "css": "circle",
-      "code": 59485,
+      "code": 8226,
       "src": "fontawesome"
     },
     {
       "uid": "422e07e5afb80258a9c4ed1706498f8a",
       "css": "circle-empty",
-      "code": 59484,
+      "code": 9702,
       "src": "fontawesome"
     },
     {
       "uid": "f4445feb55521283572ee88bc304f928",
       "css": "floppy",
-      "code": 59480,
+      "code": 128190,
       "src": "fontawesome"
     },
     {
@@ -2939,7 +2939,7 @@
     {
       "uid": "56a21935a5d4d79b2e91ec00f760b369",
       "css": "sort",
-      "code": 59482,
+      "code": 8597,
       "src": "fontawesome"
     }
   ]
--- a/kallithea/public/fontello/css/kallithea.css	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/public/fontello/css/kallithea.css	Thu May 07 13:30:17 2015 +0200
@@ -1,10 +1,12 @@
-@font-face {
+@charset "UTF-8";
+
+ @font-face {
   font-family: 'kallithea';
-  src: url('../font/kallithea.eot?81834162');
-  src: url('../font/kallithea.eot?81834162#iefix') format('embedded-opentype'),
-       url('../font/kallithea.woff?81834162') format('woff'),
-       url('../font/kallithea.ttf?81834162') format('truetype'),
-       url('../font/kallithea.svg?81834162#kallithea') format('svg');
+  src: url('../font/kallithea.eot?23730278');
+  src: url('../font/kallithea.eot?23730278#iefix') format('embedded-opentype'),
+       url('../font/kallithea.woff?23730278') format('woff'),
+       url('../font/kallithea.ttf?23730278') format('truetype'),
+       url('../font/kallithea.svg?23730278#kallithea') format('svg');
   font-weight: normal;
   font-style: normal;
 }
@@ -14,7 +16,7 @@
 @media screen and (-webkit-min-device-pixel-ratio:0) {
   @font-face {
     font-family: 'kallithea';
-    src: url('../font/kallithea.svg?81834162#kallithea') format('svg');
+    src: url('../font/kallithea.svg?23730278#kallithea') format('svg');
   }
 }
 */
@@ -35,7 +37,7 @@
   /* For safety - reset parent styles, that can break glyph codes*/
   font-variant: normal;
   text-transform: none;
-     
+ 
   /* fix buttons height, for twitter bootstrap */
   line-height: 1em;
  
@@ -46,102 +48,73 @@
   /* you can be more comfortable with increased icons size */
   /* font-size: 120%; */
  
+  /* Font smoothing. That was taken from TWBS */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+ 
   /* Uncomment for 3D effect */
   /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
 }
  
-.icon-database:before { content: '\e800'; } /* '' */
+.icon-circle:before { content: '\2022'; } /* '•' */
+.icon-right-open:before { content: '\2190'; } /* '←' */
+.icon-up-open:before { content: '\2191'; } /* '↑' */
+.icon-left-open:before { content: '\2192'; } /* '→' */
+.icon-down-open:before { content: '\2193'; } /* '↓' */
+.icon-code:before { content: '\2194'; } /* '↔' */
+.icon-sort:before { content: '\2195'; } /* '↕' */
+.icon-exchange:before { content: '\21c4'; } /* '⇄' */
+.icon-plus-circled:before { content: '\2295'; } /* '⊕' */
+.icon-minus-circled:before { content: '\2296'; } /* '⊖' */
+.icon-diff-added:before { content: '\229e'; } /* '⊞' */
+.icon-diff-removed:before { content: '\229f'; } /* '⊟' */
+.icon-diff-modified:before { content: '\22a1'; } /* '⊡' */
+.icon-clock:before { content: '\231a'; } /* '⌚' */
+.icon-triangle-left:before { content: '\23f4'; } /* '⏴' */
+.icon-triangle-right:before { content: '\23f5'; } /* '⏵' */
+.icon-triangle-up:before { content: '\23f6'; } /* '⏶' */
+.icon-triangle-down:before { content: '\23f7'; } /* '⏷' */
+.icon-circle-empty:before { content: '\25e6'; } /* '◦' */
+.icon-heart-empty:before { content: '\2661'; } /* '♡' */
+.icon-heart:before { content: '\2665'; } /* '♥' */
+.icon-cog-alt:before { content: '\2699'; } /* '⚙' */
+.icon-database:before { content: '\26c3'; } /* '⛃' */
+.icon-gear:before { content: '\26ed'; } /* '⛭' */
+.icon-plus:before { content: '\2795'; } /* '➕' */
+.icon-minus:before { content: '\2796'; } /* '➖' */
+.icon-resize-vertical:before { content: '\2b65'; } /* '⭥' */
+.icon-move-up:before { content: '\2b71'; } /* '⭱' */
+.icon-move-down:before { content: '\2b73'; } /* '⭳' */
 .icon-file-submodule:before { content: '\e801'; } /* '' */
 .icon-git-merge:before { content: '\e802'; } /* '' */
-.icon-move-down:before { content: '\e803'; } /* '' */
-.icon-move-up:before { content: '\e804'; } /* '' */
-.icon-diff-modified:before { content: '\e805'; } /* '' */
-.icon-diff-added:before { content: '\e806'; } /* '' */
-.icon-diff-removed:before { content: '\e807'; } /* '' */
-.icon-tools:before { content: '\e808'; } /* '' */
-.icon-cancel-circled:before { content: '\e809'; } /* '' */
-.icon-ok-circled:before { content: '\e80a'; } /* '' */
-.icon-comment:before { content: '\e80b'; } /* '' */
-.icon-comment-discussion:before { content: '\e80c'; } /* '' */
-.icon-triangle-down:before { content: '\e80d'; } /* '' */
-.icon-triangle-left:before { content: '\e80e'; } /* '' */
-.icon-triangle-right:before { content: '\e80f'; } /* '' */
-.icon-triangle-up:before { content: '\e810'; } /* '' */
 .icon-ruler:before { content: '\e811'; } /* '' */
-.icon-globe:before { content: '\e812'; } /* '' */
 .icon-download-cloud:before { content: '\e813'; } /* '' */
 .icon-upload-cloud:before { content: '\e814'; } /* '' */
 .icon-graph:before { content: '\e815'; } /* '' */
 .icon-file-zip:before { content: '\e816'; } /* '' */
-.icon-resize-vertical:before { content: '\e817'; } /* '' */
 .icon-file-code:before { content: '\e81a'; } /* '' */
-.icon-clippy:before { content: '\e81b'; } /* '' */
 .icon-doc-text-inv:before { content: '\e81c'; } /* '' */
 .icon-diff:before { content: '\e81d'; } /* '' */
 .icon-diff-ignored:before { content: '\e81e'; } /* '' */
 .icon-diff-renamed:before { content: '\e81f'; } /* '' */
 .icon-paste:before { content: '\e820'; } /* '' */
-.icon-code:before { content: '\e821'; } /* '' */
-.icon-keyhole-circled:before { content: '\e822'; } /* '' */
 .icon-file-directory:before { content: '\e823'; } /* '' */
 .icon-git-compare:before { content: '\e824'; } /* '' */
 .icon-git-pull-request:before { content: '\e825'; } /* '' */
-.icon-key:before { content: '\e826'; } /* '' */
 .icon-repo-forked:before { content: '\e827'; } /* '' */
 .icon-fork:before { content: '\e828'; } /* '' */
-.icon-trashcan:before { content: '\e829'; } /* '' */
-.icon-cancel:before { content: '\e82a'; } /* '' */
-.icon-search:before { content: '\e82b'; } /* '' */
-.icon-heart:before { content: '\e82c'; } /* '' */
-.icon-heart-empty:before { content: '\e82d'; } /* '' */
-.icon-user:before { content: '\e82e'; } /* '' */
-.icon-users:before { content: '\e82f'; } /* '' */
-.icon-ok:before { content: '\e830'; } /* '' */
-.icon-gear:before { content: '\e831'; } /* '' */
-.icon-cog-alt:before { content: '\e832'; } /* '' */
-.icon-doc:before { content: '\e833'; } /* '' */
 .icon-docs:before { content: '\e834'; } /* '' */
-.icon-doc-text:before { content: '\e835'; } /* '' */
 .icon-doc-inv:before { content: '\e836'; } /* '' */
 .icon-file-powerpoint:before { content: '\e837'; } /* '' */
-.icon-arrows-cw:before { content: '\e838'; } /* '' */
-.icon-clock:before { content: '\e839'; } /* '' */
-.icon-lock:before { content: '\e83a'; } /* '' */
-.icon-lock-open-alt:before { content: '\e83b'; } /* '' */
-.icon-tag:before { content: '\e83c'; } /* '' */
 .icon-tags:before { content: '\e83d'; } /* '' */
-.icon-book:before { content: '\e83e'; } /* '' */
 .icon-bookmark-empty:before { content: '\e83f'; } /* '' */
 .icon-bookmark:before { content: '\e840'; } /* '' */
 .icon-align-left:before { content: '\e841'; } /* '' */
-.icon-pencil:before { content: '\e842'; } /* '' */
 .icon-sliders:before { content: '\e843'; } /* '' */
-.icon-pencil-squared:before { content: '\e844'; } /* '' */
-.icon-edit:before { content: '\e845'; } /* '' */
-.icon-folder-open:before { content: '\e846'; } /* '' */
-.icon-folder-open-empty:before { content: '\e847'; } /* '' */
-.icon-down-open:before { content: '\e848'; } /* '' */
-.icon-left-open:before { content: '\e849'; } /* '' */
-.icon-right-open:before { content: '\e84a'; } /* '' */
-.icon-up-open:before { content: '\e84b'; } /* '' */
-.icon-plus-circled:before { content: '\e84c'; } /* '' */
-.icon-minus-circled:before { content: '\e84d'; } /* '' */
-.icon-block:before { content: '\e84e'; } /* '' */
-.icon-plus:before { content: '\e84f'; } /* '' */
-.icon-minus:before { content: '\e850'; } /* '' */
 .icon-eye-off:before { content: '\e851'; } /* '' */
-.icon-eye:before { content: '\e852'; } /* '' */
-.icon-folder:before { content: '\e853'; } /* '' */
-.icon-folder-empty:before { content: '\e854'; } /* '' */
 .icon-rss:before { content: '\e855'; } /* '' */
 .icon-rss-squared:before { content: '\e856'; } /* '' */
-.icon-wrench:before { content: '\e857'; } /* '' */
-.icon-floppy:before { content: '\e858'; } /* '' */
 .icon-strike:before { content: '\e859'; } /* '' */
-.icon-sort:before { content: '\e85a'; } /* '' */
-.icon-exchange:before { content: '\e85b'; } /* '' */
-.icon-circle-empty:before { content: '\e85c'; } /* '' */
-.icon-circle:before { content: '\e85d'; } /* '' */
 .icon-box:before { content: '\e85e'; } /* '' */
 .icon-right:before { content: '\e85f'; } /* '' */
 .icon-left:before { content: '\e860'; } /* '' */
@@ -150,4 +123,37 @@
 .icon-up-circled:before { content: '\e863'; } /* '' */
 .icon-up:before { content: '\e864'; } /* '' */
 .icon-down:before { content: '\e865'; } /* '' */
-.icon-down-circled:before { content: '\e866'; } /* '' */
\ No newline at end of file
+.icon-down-circled:before { content: '\e866'; } /* '' */
+.icon-globe:before { content: '🌎'; } /* '\1f30e' */
+.icon-eye:before { content: '👁'; } /* '\1f441' */
+.icon-user:before { content: '👤'; } /* '\1f464' */
+.icon-users:before { content: '👥'; } /* '\1f465' */
+.icon-floppy:before { content: '💾'; } /* '\1f4be' */
+.icon-folder-empty:before { content: '📁'; } /* '\1f4c1' */
+.icon-folder-open-empty:before { content: '📂'; } /* '\1f4c2' */
+.icon-clippy:before { content: '📋'; } /* '\1f4cb' */
+.icon-book:before { content: '📒'; } /* '\1f4d2' */
+.icon-search:before { content: '🔍'; } /* '\1f50d' */
+.icon-keyhole-circled:before { content: '🔐'; } /* '\1f510' */
+.icon-key:before { content: '🔑'; } /* '\1f511' */
+.icon-lock:before { content: '🔒'; } /* '\1f512' */
+.icon-lock-open-alt:before { content: '🔓'; } /* '\1f513' */
+.icon-tag:before { content: '🔖'; } /* '\1f516' */
+.icon-wrench:before { content: '🔧'; } /* '\1f527' */
+.icon-pencil:before { content: '🖉'; } /* '\1f589' */
+.icon-pencil-squared:before { content: '🖊'; } /* '\1f58a' */
+.icon-edit:before { content: '🖋'; } /* '\1f58b' */
+.icon-folder:before { content: '🗀'; } /* '\1f5c0' */
+.icon-folder-open:before { content: '🗁'; } /* '\1f5c1' */
+.icon-doc:before { content: '🗅'; } /* '\1f5c5' */
+.icon-doc-text:before { content: '🗈'; } /* '\1f5c8' */
+.icon-trashcan:before { content: '🗑'; } /* '\1f5d1' */
+.icon-arrows-cw:before { content: '🗘'; } /* '\1f5d8' */
+.icon-comment:before { content: '🗩'; } /* '\1f5e9' */
+.icon-comment-discussion:before { content: '🗪'; } /* '\1f5ea' */
+.icon-cancel:before { content: '🗴'; } /* '\1f5f4' */
+.icon-cancel-circled:before { content: '🗵'; } /* '\1f5f5' */
+.icon-ok:before { content: '🗸'; } /* '\1f5f8' */
+.icon-ok-circled:before { content: '🗹'; } /* '\1f5f9' */
+.icon-block:before { content: '🚫'; } /* '\1f6ab' */
+.icon-tools:before { content: '🛠'; } /* '\1f6e0' */
\ No newline at end of file
Binary file kallithea/public/fontello/font/kallithea.eot has changed
--- a/kallithea/public/fontello/font/kallithea.svg	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/public/fontello/font/kallithea.svg	Thu May 07 13:30:17 2015 +0200
@@ -6,98 +6,65 @@
 <font id="kallithea" horiz-adv-x="1000" >
 <font-face font-family="kallithea" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
 <missing-glyph horiz-adv-x="1000" />
-<glyph glyph-name="database" unicode="&#xe800;" d="m375-88c-207 0-375 56-375 126 0 37 0 79 0 125 0 10 5 21 13 31 42-54 187-94 362-94s320 40 362 94c8-10 13-21 13-31 0-37 0-75 0-125 0-70-168-126-375-126z m0 251c-207 0-375 56-375 125 0 37 0 78 0 125 0 6 3 13 6 19l0 0c2 4 4 8 7 12 42-54 187-94 362-94s320 40 362 94c3-4 5-8 7-12l0 0c4-6 6-13 6-19 0-37 0-75 0-125 0-69-168-125-375-125z m0 250c-207 0-375 56-375 125 0 19 0 40 0 62 0 20 0 41 0 63 0 69 168 125 375 125 207 0 375-56 375-125 0-20 0-41 0-63 0-19 0-39 0-62 0-69-168-125-375-125z m0 312c-138 0-250-28-250-62s112-63 250-63 250 28 250 63-112 62-250 62z" horiz-adv-x="750" />
+<glyph glyph-name="circle" unicode="&#x2022;" d="m857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="right-open" unicode="&#x2190;" d="m618 361l-414-415q-11-10-25-10t-26 10l-92 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l92 93q11 10 26 10t25-10l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
+<glyph glyph-name="up-open" unicode="&#x2191;" d="m939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-26 10l-92 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+<glyph glyph-name="left-open" unicode="&#x2192;" d="m653 682l-296-296 296-297q11-10 11-25t-11-25l-92-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 10 25 10t25-10l92-93q11-10 11-25t-11-25z" horiz-adv-x="714.3" />
+<glyph glyph-name="down-open" unicode="&#x2193;" d="m939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l92 92q11 11 26 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+<glyph glyph-name="code" unicode="&#x2194;" d="m594 663l-94-94 219-219-219-219 94-93 281 312-281 313z m-313 0l-281-313 281-312 94 93-219 219 219 219-94 94z" horiz-adv-x="875" />
+<glyph glyph-name="sort" unicode="&#x2195;" d="m571 243q0-15-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 10-11 25t11 25 25 11h500q14 0 25-11t10-25z m0 214q0-14-10-25t-25-11h-500q-15 0-25 11t-11 25 11 25l250 250q10 11 25 11t25-11l250-250q10-10 10-25z" horiz-adv-x="571.4" />
+<glyph glyph-name="exchange" unicode="&#x21c4;" d="m1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13 0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
+<glyph glyph-name="plus-circled" unicode="&#x2295;" d="m679 314v72q0 14-11 25t-25 10h-143v143q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-143h-143q-14 0-25-10t-10-25v-72q0-14 10-25t25-11h143v-142q0-15 11-25t25-11h71q15 0 25 11t11 25v142h143q14 0 25 11t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="minus-circled" unicode="&#x2296;" d="m679 314v72q0 14-11 25t-25 10h-429q-14 0-25-10t-10-25v-72q0-14 10-25t25-10h429q14 0 25 10t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="diff-added" unicode="&#x229e;" d="m500 538h-125v-125h-125v-125h125v-125h125v125h125v125h-125v125z m313 250c-32 0-719 0-750 0s-63-32-63-63 0-719 0-750 31-63 63-63 718 0 750 0 62 32 62 63 0 719 0 750-31 63-62 63z m-63-719c0-16-17-31-31-31s-545 0-563 0-31 11-31 31c0 15 0 547 0 562s16 32 31 32 548 0 563 0 31-16 31-32 0-547 0-562z" horiz-adv-x="875" />
+<glyph glyph-name="diff-removed" unicode="&#x229f;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-500 219h375v125h-375v-125z" horiz-adv-x="875" />
+<glyph glyph-name="diff-modified" unicode="&#x22a1;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-312 406c-70 0-125-56-125-125s55-125 125-125 125 56 125 125-56 125-125 125z" horiz-adv-x="875" />
+<glyph glyph-name="clock" unicode="&#x231a;" d="m500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="triangle-left" unicode="&#x23f4;" d="m0 350l375-375v750l-375-375z" horiz-adv-x="374.8" />
+<glyph glyph-name="triangle-right" unicode="&#x23f5;" d="m0 725l375-375-375-375v750z" horiz-adv-x="374.9" />
+<glyph glyph-name="triangle-up" unicode="&#x23f6;" d="m375 600l-375-375h750l-375 375z" horiz-adv-x="749.5" />
+<glyph glyph-name="triangle-down" unicode="&#x23f7;" d="m0 475l375-375 375 375h-750z" horiz-adv-x="749.5" />
+<glyph glyph-name="circle-empty" unicode="&#x25e6;" d="m429 654q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="heart-empty" unicode="&#x2661;" d="m929 517q0 46-12 80t-31 55-46 33-52 18-55 4-62-14-62-36-48-40-34-34q-10-13-27-13t-27 13q-14 15-34 34t-48 40-62 36-62 14-55-4-52-18-46-33-31-55-12-80q0-93 105-198l324-312 324 312q105 105 105 198z m71 0q0-123-128-251l-347-335q-10-10-25-10t-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192z" horiz-adv-x="1000" />
+<glyph glyph-name="heart" unicode="&#x2665;" d="m500-79q-14 0-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192q0-123-128-251l-347-335q-10-10-25-10z" horiz-adv-x="1000" />
+<glyph glyph-name="cog-alt" unicode="&#x2699;" d="m500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-9-6l-86-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-13-17-46-50t-44-33q-6 0-11 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v103q0 6 4 11t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 11 12 17 46 50t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 41-17l66 49q5 4 11 4 7 0 12-4 81-75 81-90 0-5-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q5-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 2 17 2t17-2q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
+<glyph glyph-name="database" unicode="&#x26c3;" d="m375-88c-207 0-375 56-375 126 0 37 0 79 0 125 0 10 5 21 13 31 42-54 187-94 362-94s320 40 362 94c8-10 13-21 13-31 0-37 0-75 0-125 0-70-168-126-375-126z m0 251c-207 0-375 56-375 125 0 37 0 78 0 125 0 6 3 13 6 19l0 0c2 4 4 8 7 12 42-54 187-94 362-94s320 40 362 94c3-4 5-8 7-12l0 0c4-6 6-13 6-19 0-37 0-75 0-125 0-69-168-125-375-125z m0 250c-207 0-375 56-375 125 0 19 0 40 0 62 0 20 0 41 0 63 0 69 168 125 375 125 207 0 375-56 375-125 0-20 0-41 0-63 0-19 0-39 0-62 0-69-168-125-375-125z m0 312c-138 0-250-28-250-62s112-63 250-63 250 28 250 63-112 62-250 62z" horiz-adv-x="750" />
+<glyph glyph-name="gear" unicode="&#x26ed;" d="m437 508c-87 0-158-71-158-158 0-87 71-158 158-158 88 0 158 71 158 158 0 87-70 158-158 158z m318-249l-29-68 51-100 7-14-71-70-116 55-68-29-35-106-5-15h-99l-43 121-69 28-100-50-13-7-71 70 55 116-28 69-107 35-14 4v100l121 43 28 68-51 100-7 14 71 70 116-55 68 29 35 106 5 15h99l43-121 69-28 100 50 13 7 71-70-55-116 28-69 107-34 14-5v-99l-120-44z" horiz-adv-x="875" />
+<glyph glyph-name="plus" unicode="&#x2795;" d="m786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q22 0 38-16t16-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="minus" unicode="&#x2796;" d="m786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q22 0 38-16t16-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="resize-vertical" unicode="&#x2b65;" d="m393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-26l-143-142q-10-11-25-11t-25 11l-143 142q-10 11-10 26t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
+<glyph glyph-name="move-up" unicode="&#x2b71;" d="m0 163h188v-313h250v313h187l-312 375-313-375z m0 687v-187h625v187h-625z" horiz-adv-x="625" />
+<glyph glyph-name="move-down" unicode="&#x2b73;" d="m625 538h-187v312h-250v-312h-188l313-375 312 375z m-625-688h625v188h-625v-188z" horiz-adv-x="625" />
 <glyph glyph-name="file-submodule" unicode="&#xe801;" d="m813 350c-32 0-188 0-188 0 0 31-31 63-62 63s-94 0-125 0-63-32-63-63 0-312 0-312h500s0 218 0 250-31 62-62 62z m-250-62h-125s0 16 0 31 14 31 31 31 47 0 62 0 32-15 32-31 0-31 0-31z m250 312c-32 0-329 0-344 0s-31 17-31 31 0 0 0 32-32 62-63 62-281 0-312 0-63-31-63-62 0-625 0-625h313s0 343 0 375 31 62 62 62 219 0 250 0 63-31 63-62h187s0 93 0 125-31 62-62 62z m-438 0h-312s0 16 0 31 15 32 31 32 234 0 250 0 31-17 31-32 0-31 0-31z" horiz-adv-x="875" />
 <glyph glyph-name="git-merge" unicode="&#xe802;" d="m625 413c-46 0-86-26-108-64-6 1-11 1-17 1-128 0-249 98-294 218 27 23 44 57 44 95 0 69-56 125-125 125s-125-56-125-125c0-47 25-86 63-108v-410c-38-21-63-61-63-107 0-70 56-126 125-126s125 56 125 126c0 46-25 86-62 107v225c82-87 195-145 312-145 6 0 11 0 17 1 22-38 62-63 108-63 69 0 125 56 125 125 0 69-56 125-125 125z m-500-438c-34 0-62 28-62 63 0 34 28 62 62 62 35 0 63-28 63-62 0-35-28-63-63-63z m0 625c-34 0-62 28-62 63s28 62 62 62c35 0 63-28 63-62s-28-63-63-63z m500-375c-34 0-62 28-62 63 0 34 28 62 62 62 35 0 63-28 63-62 0-35-28-63-63-63z" horiz-adv-x="750" />
-<glyph glyph-name="move-down" unicode="&#xe803;" d="m625 538h-187v312h-250v-312h-188l313-375 312 375z m-625-688h625v188h-625v-188z" horiz-adv-x="625" />
-<glyph glyph-name="move-up" unicode="&#xe804;" d="m0 163h188v-313h250v313h187l-312 375-313-375z m0 687v-187h625v187h-625z" horiz-adv-x="625" />
-<glyph glyph-name="diff-modified" unicode="&#xe805;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-312 406c-70 0-125-56-125-125s55-125 125-125 125 56 125 125-56 125-125 125z" horiz-adv-x="875" />
-<glyph glyph-name="diff-added" unicode="&#xe806;" d="m500 538h-125v-125h-125v-125h125v-125h125v125h125v125h-125v125z m313 250c-32 0-719 0-750 0s-63-32-63-63 0-719 0-750 31-63 63-63 718 0 750 0 62 32 62 63 0 719 0 750-31 63-62 63z m-63-719c0-16-17-31-31-31s-545 0-563 0-31 11-31 31c0 15 0 547 0 562s16 32 31 32 548 0 563 0 31-16 31-32 0-547 0-562z" horiz-adv-x="875" />
-<glyph glyph-name="diff-removed" unicode="&#xe807;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-500 219h375v125h-375v-125z" horiz-adv-x="875" />
-<glyph glyph-name="tools" unicode="&#xe808;" d="m280 396c16-16 80-83 80-83l35 36-55 57 105 112c0 0-47 47-26 28 20 74 1 157-55 215-56 58-135 77-206 57l120-125-31-122-119-33-120 125c-20-74-1-156 55-214 58-60 143-78 217-53z m402-121l-145-144 240-249c20-20 45-31 71-31 26 0 52 11 71 31 40 40 40 106 0 147l-237 246z m318 417l-153 158-451-466 55-57-270-279-62-33-87-142 23-23 137 90 32 64 270 279 55-57 451 466z" horiz-adv-x="1000" />
-<glyph glyph-name="cancel-circled" unicode="&#xe809;" d="m641 224q0 14-10 25l-101 101 101 101q10 11 10 25 0 15-10 26l-51 50q-10 11-25 11-15 0-25-11l-101-101-101 101q-11 11-26 11-15 0-25-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 25-11 15 0 26 11l101 101 101-101q10-11 25-11 15 0 25 11l51 50q10 11 10 26z m216 126q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="ok-circled" unicode="&#xe80a;" d="m717 440q0 16-11 26l-50 50q-11 11-25 11t-26-11l-227-227-126 126q-11 11-25 11t-26-11l-50-50q-10-10-10-26 0-15 10-25l202-202q10-10 25-10 15 0 25 10l303 303q11 10 11 25z m140-90q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="comment" unicode="&#xe80b;" d="m750 725h-625c-60 0-125-62-125-125v-375c0-125 125-125 125-125h63v-250l250 250c0 0 252 0 312 0s125 66 125 125v375c0 61-62 125-125 125z" horiz-adv-x="875" />
-<glyph glyph-name="comment-discussion" unicode="&#xe80c;" d="m250 350c0 63 0 188 0 188s-156 0-187 0-63-32-63-63 0-281 0-312 31-63 63-63 62 0 62 0v-188l190 188s158 0 187 0 61 31 61 63 0 62 0 62-125 0-188 0-125 63-125 125z m563 375c-32 0-407 0-438 0s-62-31-62-62 0-282 0-313 31-62 62-62 186 0 186 0l189-188v188s31 0 63 0 62 31 62 62 0 281 0 313-31 62-62 62z" horiz-adv-x="875" />
-<glyph glyph-name="triangle-down" unicode="&#xe80d;" d="m0 475l375-375 375 375h-750z" horiz-adv-x="749.5" />
-<glyph glyph-name="triangle-left" unicode="&#xe80e;" d="m0 350l375-375v750l-375-375z" horiz-adv-x="374.8" />
-<glyph glyph-name="triangle-right" unicode="&#xe80f;" d="m0 725l375-375-375-375v750z" horiz-adv-x="374.9" />
-<glyph glyph-name="triangle-up" unicode="&#xe810;" d="m375 600l-375-375h750l-375 375z" horiz-adv-x="749.5" />
 <glyph glyph-name="ruler" unicode="&#xe811;" d="m125 267h42v41h-42v-41z m42 416h-42v-41h42v41z m0-750h-42v-41h-83v-42h208v42h-83v41z m-42 584h42v41h-42v-41z m0-125h42v41h-42v-41z m250 458v-1000h542v1000h-542z m333-917h-250v834h250v-834z m125 0h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m0 125h-83v84h83v-84z m-791 125h83v-41h42v41h83v42h-208v-42z m83-791h42v41h-42v-41z m0 125h42v41h-42v-41z" horiz-adv-x="1000" />
-<glyph glyph-name="globe" unicode="&#xe812;" d="m500 725c-207 0-375-168-375-375s168-375 375-375c25 0 50 2 74 7-10 5-11 40-1 59 11 22 44 78 11 97s-24 27-44 48-12 25-13 31c-5 19 19 47 20 50s1 14 1 17-15 13-19 13-5-6-10-6-28 13-32 17-7 12-14 19-7 1-18 5-43 17-68 27-28 24-28 35-15 25-22 35c-7 11-9 25-11 22s13-42 10-43-8 11-15 20 8 5-16 51 8 70 9 94 20-9 10 6 1 48-6 60-49-14-49-14c1 12 36 31 62 49s41 4 62-2 22-5 15 2 3 10 19 7 20-22 45-20 2-5 6-11-4-6-20-17 0-10 29-31 20 14 17 29 21 3 21 3c17-11 14 0 27-4s47-34 47-34c-43-24-16-26-8-32s-16-16-16-16c-9 9-10 0-16-3s0-12 0-12c-31-5-24-37-23-45s-20-19-25-30 13-35 4-36-19 36-71 22c-15-4-49-22-31-58s49 10 59 5-3-28-1-29 29-1 31-32 40-29 49-29 36 23 40 24 20 14 56-6 53-17 65-25 3-26 15-31 56 2 68-17-47-112-65-123-27-33-45-48-44-34-68-48c-22-13-26-36-35-43 168 37 293 187 293 366 0 207-168 375-375 375z m88-352c-5-1-16-11-42 5s-44 12-46 15c0 0-2 6 9 7 24 2 53-22 60-22s9 6 21 3c12-4 3-6-2-8z m-123 315c-2 2 2 4 5 7 2 3 1 6 3 8 5 6 32 13 27-2-5-15-31-16-35-13z m66-48c-9 0-31 3-27 7 16 15-6 19-19 21s-19 8-12 9 33-1 37-4 29-14 30-20 0-13-9-13z m79 3c-7-6-44 21-51 27-31 26-47 17-54 22-6 4-4 10 6 19s38-3 54-5 35-14 35-29c0-15 18-28 10-34z" horiz-adv-x="1000" />
 <glyph glyph-name="download-cloud" unicode="&#xe813;" d="m714 332q0 8-5 13t-13 5h-125v196q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-196h-125q-8 0-13-5t-5-13q0-8 5-13l196-196q5-5 13-5t13 5l196 196q5 6 5 13z m357-125q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" />
 <glyph glyph-name="upload-cloud" unicode="&#xe814;" d="m714 368q0 8-5 13l-196 196q-5 5-13 5t-13-5l-196-196q-5-6-5-13 0-8 5-13t13-5h125v-196q0-8 5-13t12-5h108q7 0 12 5t5 13v196h125q8 0 13 5t5 13z m357-161q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" />
 <glyph glyph-name="graph" unicode="&#xe815;" d="m688 600h-188v-625h188v625z m250-187h-188v-438h188v438z m-875-501v126h62v62h-62v125h62v63h-62v125h62v62h-62v125h62v63h-62v125h62v62h-125v-1000h1000v62h-937z m375 376h-188v-313h188v313z" horiz-adv-x="1000" />
 <glyph glyph-name="file-zip" unicode="&#xe816;" d="m313 288v62h-63v-62h63z m0 125v62h-63v-62h63z m0 125v62h-63v-62h63z m-125-63h62v63h-62v-63z m375 313h-563v-876h750v688l-187 188z m125-813h-625v750h187v-62h63v62h187l188-187v-563z m-500 625h62v63h-62v-63z m0-250h62v63h-62v-63z m0-125l-63-62v-125h250v125l-62 62h-63v63h-62v-63z m125-62v-63h-125v63h125z" horiz-adv-x="750" />
-<glyph glyph-name="resize-vertical" unicode="&#xe817;" d="m393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-26l-143-142q-10-11-25-11t-25 11l-143 142q-10 11-10 26t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
 <glyph glyph-name="file-code" unicode="&#xe81a;" d="m281 475l-156-156 156-156 63 62-94 94 94 94-63 62z m125-62l94-94-94-94 63-62 156 156-156 156-63-62z m157 375h-563v-876h750v688l-187 188z m125-813h-625v750h437l188-187v-563z" horiz-adv-x="750" />
-<glyph glyph-name="clippy" unicode="&#xe81b;" d="m688-25h-625v563h625v-188h62v313c0 34-28 62-62 62h-188c0 69-56 125-125 125s-125-56-125-125h-187c-35 0-63-28-63-62v-688c0-34 28-63 63-63h625c34 0 62 29 62 63v125h-62v-125z m-500 688c28 0 28 0 62 0s63 28 63 62 28 63 62 63 63-29 63-63 31-62 62-62 32 0 63 0 62-29 62-63h-500c0 38 27 63 63 63z m-63-500h125v62h-125v-62z m438 125v125l-250-188 250-187v125h312v125h-312z m-438-250h188v62h-188v-62z m313 437h-313v-62h313v62z m-188-125h-125v-62h125v62z" horiz-adv-x="875" />
 <glyph glyph-name="doc-text-inv" unicode="&#xe81c;" d="m819 584q8-7 16-20h-264v264q13-8 21-16z m-265-91h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304q0-22 16-38t38-15z m89-411v36q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-393q-8 0-13-5t-5-12v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-35q0-8 5-13t13-5h393q8 0 13 5t5 13z" horiz-adv-x="857.1" />
 <glyph glyph-name="diff" unicode="&#xe81d;" d="m438 600h-125v-125h-125v-125h125v-125h125v125h125v125h-125v125z m-250-625h375v125h-375v-125z m437 875h-500v-62h469l219-219v-594h62v625l-250 250z m-625-125v-875h750v688l-187 187h-563z m688-813h-625v751h468l157-157v-594z" horiz-adv-x="875" />
 <glyph glyph-name="diff-ignored" unicode="&#xe81e;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-500 189v-95h96l279 279v96h-96l-279-280z" horiz-adv-x="875" />
 <glyph glyph-name="diff-renamed" unicode="&#xe81f;" d="m813 788h-750c-32 0-63-32-63-63v-750c0-31 31-63 63-63h750c31 0 62 32 62 63v750c0 31-31 63-62 63z m-63-719c0-16-17-31-31-31h-563c-17 0-31 11-31 31v562c0 16 16 32 31 32h563c14 0 31-16 31-32v-562z m-312 344h-188v-125h188v-125l250 187-250 188v-125z" horiz-adv-x="875" />
 <glyph glyph-name="paste" unicode="&#xe820;" d="m429-79h500v358h-233q-22 0-38 15t-15 38v232h-214v-643z m142 804v36q0 7-5 12t-12 6h-393q-7 0-13-6t-5-12v-36q0-7 5-13t13-5h393q7 0 12 5t5 13z m143-375h167l-167 167v-167z m286-71v-375q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v89h-303q-23 0-38 16t-16 37v750q0 23 16 38t38 16h607q22 0 38-16t15-38v-183q12-7 20-15l228-228q16-16 27-42t11-50z" horiz-adv-x="1000" />
-<glyph glyph-name="code" unicode="&#xe821;" d="m594 663l-94-94 219-219-219-219 94-93 281 312-281 313z m-313 0l-281-313 281-312 94 93-219 219 219 219-94 94z" horiz-adv-x="875" />
-<glyph glyph-name="keyhole-circled" unicode="&#xe822;" d="m500 734c-212 0-384-172-384-384 0-212 172-384 384-384 212 0 384 172 384 384 0 212-172 384-384 384z m131-646h-262l57 285c-34 24-57 63-57 108 0 72 59 131 131 131 72 0 131-59 131-131 0-45-23-84-57-108l57-285z" horiz-adv-x="1000" />
 <glyph glyph-name="file-directory" unicode="&#xe823;" d="m813 663c-32 0-329 0-344 0s-31 15-31 31 0 0 0 31-32 63-63 63-281 0-312 0-63-32-63-63 0-687 0-687h875s0 531 0 562-31 63-62 63z m-438 0h-312s0 14 0 31 15 31 31 31 235 0 250 0 31-15 31-31 0-31 0-31z" horiz-adv-x="875" />
 <glyph glyph-name="git-compare" unicode="&#xe824;" d="m813 145s0 299 0 393-94 187-188 187c-62 0-62 0-62 0v125l-188-187 188-188v125s31 0 62 0 63-31 63-62 0-393 0-393c-38-22-63-62-63-107 0-70 56-126 125-126s125 56 125 126c0 45-25 85-62 107z m-63-170c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z m-437 125s-32 0-63 0-62 31-62 63 0 392 0 392c37 22 62 62 62 108 0 69-56 125-125 125s-125-56-125-125c0-46 25-86 63-108 0 0 0-299 0-392s93-188 187-188c63 0 63 0 63 0v-125l187 188-187 187v-125z m-188 500c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z" horiz-adv-x="875" />
 <glyph glyph-name="git-pull-request" unicode="&#xe825;" d="m688 145s0 299 0 393-94 187-188 187c-62 0-62 0-62 0v125l-188-187 188-188v125s31 0 62 0 63-31 63-62 0-393 0-393c-38-22-63-62-63-107 0-70 56-126 125-126s125 56 125 126c0 45-25 85-62 107z m-63-170c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z m-500 813c-69 0-125-56-125-125 0-46 25-86 63-108v-409c-38-22-63-62-63-107 0-70 56-126 125-126s125 56 125 126c0 45-25 85-62 107v409c37 22 62 62 62 108 0 69-56 125-125 125z m0-813c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z m0 625c-34 0-62 28-62 63s28 62 62 62 63-28 63-62-29-63-63-63z" horiz-adv-x="750" />
-<glyph glyph-name="key" unicode="&#xe826;" d="m626 788c-138 0-250-112-250-250 0-19 2-38 6-56l-382-382v-62l63-63h125l62 63v62h63v63h62v62h125l69 69c18-4 37-6 57-6 138 0 250 112 250 250s-112 250-250 250z m-251-438l-312-312v62l312 313v-63z m313 188c-35 0-63 28-63 62 0 35 28 63 63 63s62-28 62-63c0-34-28-62-62-62z" horiz-adv-x="875.9" />
 <glyph glyph-name="repo-forked" unicode="&#xe827;" d="m750 725c0 69-56 125-125 125s-125-56-125-125c0-46 25-87 63-108v-104l-188-207-187 207v104c37 21 62 61 62 108 0 69-56 125-125 125s-125-56-125-125c0-46 25-87 63-108v-153l250-275v-107c-38-21-63-61-63-108 0-69 56-125 125-125s125 56 125 125c0 46-25 87-62 108v107l250 275v153c37 21 62 61 62 108z m-625 62c33 0 61-28 61-61s-28-61-61-61-60 28-60 61 27 61 60 61z m250-871c-33 0-60 28-60 61s27 61 60 61 61-28 61-61-28-61-61-61z m250 871c33 0 61-28 61-61s-28-61-61-61-60 28-60 61 27 61 60 61z" horiz-adv-x="750" />
 <glyph glyph-name="fork" unicode="&#xe828;" d="m161 29q0 22-16 38t-38 15-38-15-15-38 15-38 38-16 38 16 16 38z m0 642q0 23-16 38t-38 16-38-16-15-38 15-38 38-15 38 15 16 38z m357-71q0 22-16 38t-38 16-38-16-15-38 15-38 38-16 38 16 16 38z m53 0q0-29-14-54t-39-39q-1-160-126-231-38-21-114-45-71-22-94-39t-23-56v-15q24-14 39-39t14-53q0-45-31-76t-76-32-76 32-31 76q0 29 15 53t39 39v458q-25 14-39 39t-15 53q0 45 31 76t76 32 76-32 31-76q0-29-14-53t-39-39v-278q30 15 86 32 30 10 49 17t39 17 33 22 22 29 16 38 5 51q-25 14-39 39t-15 54q0 45 31 76t76 31 76-31 31-76z" horiz-adv-x="571.4" />
-<glyph glyph-name="trashcan" unicode="&#xe829;" d="m688 725h-250c0 0 0 24 0 31 0 18-14 32-32 32s-31-14-31-32c0-17 0-31 0-31h-250c-34 0-62-28-62-62v-63c0-34 28-62 62-62v-563c0-35 28-63 63-63h437c35 0 63 28 63 63v563c34 0 62 28 62 62v63c0 34-28 62-62 62z m-63-719c0-17-14-31-31-31h-375c-17 0-31 14-31 31v532h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62l0-469c0-17 14-31 31-31s32 14 32 31v469h62v-532z m63 610c0-9-7-16-16-16h-531c-9 0-16 7-16 16v31c0 9 7 16 16 16h531c9 0 16-7 16-16v-31z" horiz-adv-x="750" />
-<glyph glyph-name="cancel" unicode="&#xe82a;" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
-<glyph glyph-name="search" unicode="&#xe82b;" d="m938 38l-244 243c35 57 56 123 56 194 0 207-168 375-375 375-207 0-375-168-375-375 0-207 168-375 375-375 71 0 138 21 194 56l244-244c17-17 45-17 62 0l63 63c17 17 17 45 0 63z m-563 187c-138 0-250 112-250 250s112 250 250 250 250-112 250-250-112-250-250-250z" horiz-adv-x="950.3" />
-<glyph glyph-name="heart" unicode="&#xe82c;" d="m500-79q-14 0-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192q0-123-128-251l-347-335q-10-10-25-10z" horiz-adv-x="1000" />
-<glyph glyph-name="heart-empty" unicode="&#xe82d;" d="m929 517q0 46-12 80t-31 55-46 33-52 18-55 4-62-14-62-36-48-40-34-34q-10-13-27-13t-27 13q-14 15-34 34t-48 40-62 36-62 14-55-4-52-18-46-33-31-55-12-80q0-93 105-198l324-312 324 312q105 105 105 198z m71 0q0-123-128-251l-347-335q-10-10-25-10t-25 10l-348 336q-5 5-15 15t-31 36-38 55-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192z" horiz-adv-x="1000" />
-<glyph glyph-name="user" unicode="&#xe82e;" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />
-<glyph glyph-name="users" unicode="&#xe82f;" d="m331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
-<glyph glyph-name="ok" unicode="&#xe830;" d="m932 534q0-22-15-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q15-16 15-38z" horiz-adv-x="1000" />
-<glyph glyph-name="gear" unicode="&#xe831;" d="m437 508c-87 0-158-71-158-158 0-87 71-158 158-158 88 0 158 71 158 158 0 87-70 158-158 158z m318-249l-29-68 51-100 7-14-71-70-116 55-68-29-35-106-5-15h-99l-43 121-69 28-100-50-13-7-71 70 55 116-28 69-107 35-14 4v100l121 43 28 68-51 100-7 14 71 70 116-55 68 29 35 106 5 15h99l43-121 69-28 100 50 13 7 71-70-55-116 28-69 107-34 14-5v-99l-120-44z" horiz-adv-x="875" />
-<glyph glyph-name="cog-alt" unicode="&#xe832;" d="m500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-9-6l-86-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-13-17-46-50t-44-33q-6 0-11 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v103q0 6 4 11t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 11 12 17 46 50t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 41-17l66 49q5 4 11 4 7 0 12-4 81-75 81-90 0-5-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q5-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 2 17 2t17-2q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
-<glyph glyph-name="doc" unicode="&#xe833;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z" horiz-adv-x="857.1" />
 <glyph glyph-name="docs" unicode="&#xe834;" d="m946 636q23 0 38-16t16-38v-678q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v160h-303q-23 0-38 16t-16 38v375q0 22 11 49t27 42l228 228q15 16 42 27t49 11h232q23 0 38-16t16-38v-183q38 23 71 23h232z m-303-119l-167-167h167v167z m-357 214l-167-167h167v167z m109-361l176 176v233h-214v-233q0-22-15-38t-38-15h-233v-357h286v143q0 22 11 49t27 42z m534-449v643h-215v-232q0-22-15-38t-38-15h-232v-358h500z" horiz-adv-x="1000" />
-<glyph glyph-name="doc-text" unicode="&#xe835;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
 <glyph glyph-name="doc-inv" unicode="&#xe836;" d="m571 564v264q13-8 21-16l227-228q8-7 16-20h-264z m-71-18q0-22 16-38t38-15h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304z" horiz-adv-x="857.1" />
 <glyph glyph-name="file-powerpoint" unicode="&#xe837;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-554 131v-59h183v59h-52v93h76q43 0 66 9 37 12 59 48t23 82q0 45-21 78t-56 49q-27 10-72 10h-206v-59h52v-310h-52z m197 156h-66v150h67q29 0 46-10 31-19 31-64 0-50-34-67-18-9-44-9z" horiz-adv-x="857.1" />
-<glyph glyph-name="arrows-cw" unicode="&#xe838;" d="m843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-37 90-57t105-20q74 0 139 37t104 99q6 10 29 66 5 13 17 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
-<glyph glyph-name="clock" unicode="&#xe839;" d="m500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="lock" unicode="&#xe83a;" d="m179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
-<glyph glyph-name="lock-open-alt" unicode="&#xe83b;" d="m589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-15-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
-<glyph glyph-name="tag" unicode="&#xe83c;" d="m250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" />
 <glyph glyph-name="tags" unicode="&#xe83d;" d="m250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z m215 0q0-30-21-51l-274-274q-22-21-51-21-20 0-33 8t-29 25l262 262q21 21 21 51 0 29-21 50l-399 399q-21 21-57 36t-65 15h125q29 0 65-15t57-36l399-399q21-21 21-50z" horiz-adv-x="1071.4" />
-<glyph glyph-name="book" unicode="&#xe83e;" d="m915 583q22-32 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 63-32t10-72l-152-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t19 23l168 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-2 12t-11 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-8 0-14-5t-10-13z" horiz-adv-x="928.6" />
 <glyph glyph-name="bookmark-empty" unicode="&#xe83f;" d="m643 707h-572v-693l237 227 49 47 50-47 236-227v693z m7 72q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
 <glyph glyph-name="bookmark" unicode="&#xe840;" d="m650 779q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
 <glyph glyph-name="align-left" unicode="&#xe841;" d="m1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-857q-15 0-25 11t-11 25v72q0 14 11 25t25 10h857q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-643q-15 0-25 10t-11 25v72q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1000" />
-<glyph glyph-name="pencil" unicode="&#xe842;" d="m203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
 <glyph glyph-name="sliders" unicode="&#xe843;" d="m196 64v-71h-196v71h196z m197 72q14 0 25-11t11-25v-143q0-14-11-25t-25-11h-143q-14 0-25 11t-11 25v143q0 15 11 25t25 11h143z m89 214v-71h-482v71h482z m-357 286v-72h-125v72h125z m732-572v-71h-411v71h411z m-536 643q15 0 26-10t10-26v-142q0-15-10-26t-26-10h-142q-15 0-26 10t-10 26v142q0 15 10 26t26 10h142z m358-286q14 0 25-10t10-25v-143q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v143q0 14 11 25t25 10h143z m178-71v-71h-125v71h125z m0 286v-72h-482v72h482z" horiz-adv-x="857.1" />
-<glyph glyph-name="pencil-squared" unicode="&#xe844;" d="m225 232l85-85-29-29h-31v53h-54v32z m231 217q8-7-1-16l-163-163q-9-9-16-1-8 7 1 16l163 163q9 9 16 1z m-152-385l303 304-161 161-303-304v-161h161z m339 340l51 51q16 16 16 38t-16 38l-85 85q-15 15-38 15t-38-15l-51-52z m214 214v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
-<glyph glyph-name="edit" unicode="&#xe845;" d="m496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 51q16 16 38 16t38-16l85-84q16-16 16-38t-16-38z" horiz-adv-x="1000" />
-<glyph glyph-name="folder-open" unicode="&#xe846;" d="m1049 319q0-18-18-37l-187-221q-24-28-67-48t-81-20h-607q-19 0-33 7t-15 24q0 17 17 37l188 221q24 28 67 48t80 20h607q19 0 34-7t15-24z m-192 192v-90h-464q-53 0-110-26t-92-67l-188-221-2-3q0 2-1 7t0 7v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88z" horiz-adv-x="1071.4" />
-<glyph glyph-name="folder-open-empty" unicode="&#xe847;" d="m994 330q0 20-30 20h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q22 0 48 13t40 29l164 203q10 12 10 21z m-637 91h429v90q0 22-16 38t-38 15h-321q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-476l143 175q25 30 65 49t78 19z m708-91q0-34-25-66l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-38z" horiz-adv-x="1071.4" />
-<glyph glyph-name="down-open" unicode="&#xe848;" d="m939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l92 92q11 11 26 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
-<glyph glyph-name="left-open" unicode="&#xe849;" d="m653 682l-296-296 296-297q11-10 11-25t-11-25l-92-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 10 25 10t25-10l92-93q11-10 11-25t-11-25z" horiz-adv-x="714.3" />
-<glyph glyph-name="right-open" unicode="&#xe84a;" d="m618 361l-414-415q-11-10-25-10t-26 10l-92 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l92 93q11 10 26 10t25-10l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
-<glyph glyph-name="up-open" unicode="&#xe84b;" d="m939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-26 10l-92 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
-<glyph glyph-name="plus-circled" unicode="&#xe84c;" d="m679 314v72q0 14-11 25t-25 10h-143v143q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-143h-143q-14 0-25-10t-10-25v-72q0-14 10-25t25-11h143v-142q0-15 11-25t25-11h71q15 0 25 11t11 25v142h143q14 0 25 11t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="minus-circled" unicode="&#xe84d;" d="m679 314v72q0 14-11 25t-25 10h-429q-14 0-25-10t-10-25v-72q0-14 10-25t25-10h429q14 0 25 10t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="block" unicode="&#xe84e;" d="m732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-112-41-152q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
-<glyph glyph-name="plus" unicode="&#xe84f;" d="m786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q22 0 38-16t16-38z" horiz-adv-x="785.7" />
-<glyph glyph-name="minus" unicode="&#xe850;" d="m786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q22 0 38-16t16-38z" horiz-adv-x="785.7" />
 <glyph glyph-name="eye-off" unicode="&#xe851;" d="m310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-92l157 281q4-26 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-86t81-102q11-19 11-39z" horiz-adv-x="1000" />
-<glyph glyph-name="eye" unicode="&#xe852;" d="m929 314q-85 132-213 197 34-58 34-125 0-104-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 242 68 187 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
-<glyph glyph-name="folder" unicode="&#xe853;" d="m929 511v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
-<glyph glyph-name="folder-empty" unicode="&#xe854;" d="m857 118v393q0 22-15 38t-38 15h-393q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-536q0-22 16-38t38-16h679q22 0 38 16t15 38z m72 393v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
 <glyph glyph-name="rss" unicode="&#xe855;" d="m214 100q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m286-69q1-15-9-26-11-12-27-12h-75q-14 0-24 9t-11 23q-12 128-103 219t-219 103q-14 1-23 11t-9 24v75q0 16 12 26 9 10 24 10h3q89-7 170-45t145-101q63-63 101-145t45-171z m286-1q1-15-10-26-10-11-26-11h-80q-14 0-25 10t-11 23q-6 120-56 228t-129 188-188 129-227 57q-14 0-24 11t-10 24v80q0 15 11 26 10 10 25 10h1q147-8 280-67t238-164q104-104 164-238t67-280z" horiz-adv-x="785.7" />
 <glyph glyph-name="rss-squared" unicode="&#xe856;" d="m286 136q0 29-21 50t-51 21-50-21-21-50 21-51 50-21 51 21 21 51z m196-53q-8 130-99 221t-221 99q-8 1-14-5t-5-13v-71q0-8 5-13t12-5q86-6 147-68t67-147q1-7 6-12t12-5h72q7 0 13 6t5 13z m214 0q-3 86-31 166t-78 145-115 114-145 78-166 31q-8 1-13-5-5-5-5-13v-71q0-7 5-12t12-6q114-4 211-62t156-155 62-211q0-8 5-13t13-5h71q7 0 13 6 6 5 5 13z m161 535v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
-<glyph glyph-name="wrench" unicode="&#xe857;" d="m214 29q0 14-10 25t-25 10-26-10-10-25 10-26 26-10 25 10 10 26z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m353 243q0-22-12-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t4-14z" horiz-adv-x="928.6" />
-<glyph glyph-name="floppy" unicode="&#xe858;" d="m214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-8 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z" horiz-adv-x="857.1" />
 <glyph glyph-name="strike" unicode="&#xe859;" d="m982 350q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12v36q0 8 5 13t13 5h964z m-712 36q-16 19-29 44-27 54-27 105 0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-102-33-37-32-37-81 0-41 37-79t156-72q38-11 96-36 33-16 53-29h-414z m282-143h230q4-22 4-51 0-62-23-119-13-30-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 8-40 15-5 5-5 12v8q0 60-1 87 0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-77 40z" horiz-adv-x="1000" />
-<glyph glyph-name="sort" unicode="&#xe85a;" d="m571 243q0-15-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 10-11 25t11 25 25 11h500q14 0 25-11t10-25z m0 214q0-14-10-25t-25-11h-500q-15 0-25 11t-11 25 11 25l250 250q10 11 25 11t25-11l250-250q10-10 10-25z" horiz-adv-x="571.4" />
-<glyph glyph-name="exchange" unicode="&#xe85b;" d="m1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13 0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
-<glyph glyph-name="circle-empty" unicode="&#xe85c;" d="m429 654q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-<glyph glyph-name="circle" unicode="&#xe85d;" d="m857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
 <glyph glyph-name="box" unicode="&#xe85e;" d="m607 386q0 14-10 25t-26 10h-142q-15 0-26-10t-10-25 10-25 26-11h142q15 0 26 11t10 25z m322 107v-536q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v536q0 14 11 25t25 11h786q14 0 25-11t11-25z m35 250v-143q0-15-10-25t-25-11h-858q-14 0-25 11t-10 25v143q0 14 10 25t25 11h858q14 0 25-11t10-25z" horiz-adv-x="1000" />
 <glyph glyph-name="right" unicode="&#xe85f;" d="m964 352q0-8-5-14l-215-197q-9-8-19-4-11 5-11 17v125h-696q-8 0-13 5t-5 12v108q0 7 5 12t13 5h696v125q0 12 11 17t19-3l215-196q5-5 5-12z" horiz-adv-x="1000" />
 <glyph glyph-name="left" unicode="&#xe860;" d="m1000 404v-108q0-7-5-12t-13-5h-696v-125q0-12-11-17t-19 3l-215 196q-5 5-5 12 0 8 5 14l215 197q9 8 19 4 11-5 11-17v-125h696q8 0 13-5t5-12z" horiz-adv-x="1000" />
@@ -107,6 +74,39 @@
 <glyph glyph-name="up" unicode="&#xe864;" d="m427 575q-5-11-16-11h-125v-696q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v696h-125q-12 0-16 11t3 19l195 215q5 5 13 5 7 0 13-5l198-215q7-9 3-19z" horiz-adv-x="428.6" />
 <glyph glyph-name="down" unicode="&#xe865;" d="m427 125q4-10-3-19l-195-215q-6-5-13-5-8 0-13 5l-198 215q-8 9-3 19 5 11 16 11h125v696q0 8 5 13t13 5h107q8 0 13-5t5-13v-696h125q11 0 16-11z" horiz-adv-x="428.6" />
 <glyph glyph-name="down-circled" unicode="&#xe866;" d="m625 332q0-7-6-13l-178-178q-6-5-12-5t-13 5l-179 178q-8 9-4 20 5 11 17 11h107v196q0 8 5 13t13 5h107q8 0 13-5t5-13v-196h107q8 0 13-5t5-13z m-196 322q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="globe" unicode="&#x1f30e;" d="m500 725c-207 0-375-168-375-375s168-375 375-375c25 0 50 2 74 7-10 5-11 40-1 59 11 22 44 78 11 97s-24 27-44 48-12 25-13 31c-5 19 19 47 20 50s1 14 1 17-15 13-19 13-5-6-10-6-28 13-32 17-7 12-14 19-7 1-18 5-43 17-68 27-28 24-28 35-15 25-22 35c-7 11-9 25-11 22s13-42 10-43-8 11-15 20 8 5-16 51 8 70 9 94 20-9 10 6 1 48-6 60-49-14-49-14c1 12 36 31 62 49s41 4 62-2 22-5 15 2 3 10 19 7 20-22 45-20 2-5 6-11-4-6-20-17 0-10 29-31 20 14 17 29 21 3 21 3c17-11 14 0 27-4s47-34 47-34c-43-24-16-26-8-32s-16-16-16-16c-9 9-10 0-16-3s0-12 0-12c-31-5-24-37-23-45s-20-19-25-30 13-35 4-36-19 36-71 22c-15-4-49-22-31-58s49 10 59 5-3-28-1-29 29-1 31-32 40-29 49-29 36 23 40 24 20 14 56-6 53-17 65-25 3-26 15-31 56 2 68-17-47-112-65-123-27-33-45-48-44-34-68-48c-22-13-26-36-35-43 168 37 293 187 293 366 0 207-168 375-375 375z m88-352c-5-1-16-11-42 5s-44 12-46 15c0 0-2 6 9 7 24 2 53-22 60-22s9 6 21 3c12-4 3-6-2-8z m-123 315c-2 2 2 4 5 7 2 3 1 6 3 8 5 6 32 13 27-2-5-15-31-16-35-13z m66-48c-9 0-31 3-27 7 16 15-6 19-19 21s-19 8-12 9 33-1 37-4 29-14 30-20 0-13-9-13z m79 3c-7-6-44 21-51 27-31 26-47 17-54 22-6 4-4 10 6 19s38-3 54-5 35-14 35-29c0-15 18-28 10-34z" horiz-adv-x="1000" />
+<glyph glyph-name="eye" unicode="&#x1f441;" d="m929 314q-85 132-213 197 34-58 34-125 0-104-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 242 68 187 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
+<glyph glyph-name="user" unicode="&#x1f464;" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />
+<glyph glyph-name="users" unicode="&#x1f465;" d="m331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
+<glyph glyph-name="floppy" unicode="&#x1f4be;" d="m214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-8 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z" horiz-adv-x="857.1" />
+<glyph glyph-name="folder-empty" unicode="&#x1f4c1;" d="m857 118v393q0 22-15 38t-38 15h-393q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-536q0-22 16-38t38-16h679q22 0 38 16t15 38z m72 393v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
+<glyph glyph-name="folder-open-empty" unicode="&#x1f4c2;" d="m994 330q0 20-30 20h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q22 0 48 13t40 29l164 203q10 12 10 21z m-637 91h429v90q0 22-16 38t-38 15h-321q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-476l143 175q25 30 65 49t78 19z m708-91q0-34-25-66l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-38z" horiz-adv-x="1071.4" />
+<glyph glyph-name="clippy" unicode="&#x1f4cb;" d="m688-25h-625v563h625v-188h62v313c0 34-28 62-62 62h-188c0 69-56 125-125 125s-125-56-125-125h-187c-35 0-63-28-63-62v-688c0-34 28-63 63-63h625c34 0 62 29 62 63v125h-62v-125z m-500 688c28 0 28 0 62 0s63 28 63 62 28 63 62 63 63-29 63-63 31-62 62-62 32 0 63 0 62-29 62-63h-500c0 38 27 63 63 63z m-63-500h125v62h-125v-62z m438 125v125l-250-188 250-187v125h312v125h-312z m-438-250h188v62h-188v-62z m313 437h-313v-62h313v62z m-188-125h-125v-62h125v62z" horiz-adv-x="875" />
+<glyph glyph-name="book" unicode="&#x1f4d2;" d="m915 583q22-32 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 63-32t10-72l-152-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t19 23l168 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-2 12t-11 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-8 0-14-5t-10-13z" horiz-adv-x="928.6" />
+<glyph glyph-name="search" unicode="&#x1f50d;" d="m938 38l-244 243c35 57 56 123 56 194 0 207-168 375-375 375-207 0-375-168-375-375 0-207 168-375 375-375 71 0 138 21 194 56l244-244c17-17 45-17 62 0l63 63c17 17 17 45 0 63z m-563 187c-138 0-250 112-250 250s112 250 250 250 250-112 250-250-112-250-250-250z" horiz-adv-x="950.3" />
+<glyph glyph-name="keyhole-circled" unicode="&#x1f510;" d="m500 734c-212 0-384-172-384-384 0-212 172-384 384-384 212 0 384 172 384 384 0 212-172 384-384 384z m131-646h-262l57 285c-34 24-57 63-57 108 0 72 59 131 131 131 72 0 131-59 131-131 0-45-23-84-57-108l57-285z" horiz-adv-x="1000" />
+<glyph glyph-name="key" unicode="&#x1f511;" d="m626 788c-138 0-250-112-250-250 0-19 2-38 6-56l-382-382v-62l63-63h125l62 63v62h63v63h62v62h125l69 69c18-4 37-6 57-6 138 0 250 112 250 250s-112 250-250 250z m-251-438l-312-312v62l312 313v-63z m313 188c-35 0-63 28-63 62 0 35 28 63 63 63s62-28 62-63c0-34-28-62-62-62z" horiz-adv-x="875.9" />
+<glyph glyph-name="lock" unicode="&#x1f512;" d="m179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
+<glyph glyph-name="lock-open-alt" unicode="&#x1f513;" d="m589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-15-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
+<glyph glyph-name="tag" unicode="&#x1f516;" d="m250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" />
+<glyph glyph-name="wrench" unicode="&#x1f527;" d="m214 29q0 14-10 25t-25 10-26-10-10-25 10-26 26-10 25 10 10 26z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m353 243q0-22-12-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t4-14z" horiz-adv-x="928.6" />
+<glyph glyph-name="pencil" unicode="&#x1f589;" d="m203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
+<glyph glyph-name="pencil-squared" unicode="&#x1f58a;" d="m225 232l85-85-29-29h-31v53h-54v32z m231 217q8-7-1-16l-163-163q-9-9-16-1-8 7 1 16l163 163q9 9 16 1z m-152-385l303 304-161 161-303-304v-161h161z m339 340l51 51q16 16 16 38t-16 38l-85 85q-15 15-38 15t-38-15l-51-52z m214 214v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
+<glyph glyph-name="edit" unicode="&#x1f58b;" d="m496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 51q16 16 38 16t38-16l85-84q16-16 16-38t-16-38z" horiz-adv-x="1000" />
+<glyph glyph-name="folder" unicode="&#x1f5c0;" d="m929 511v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
+<glyph glyph-name="folder-open" unicode="&#x1f5c1;" d="m1049 319q0-18-18-37l-187-221q-24-28-67-48t-81-20h-607q-19 0-33 7t-15 24q0 17 17 37l188 221q24 28 67 48t80 20h607q19 0 34-7t15-24z m-192 192v-90h-464q-53 0-110-26t-92-67l-188-221-2-3q0 2-1 7t0 7v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q51 0 88-37t37-88z" horiz-adv-x="1071.4" />
+<glyph glyph-name="doc" unicode="&#x1f5c5;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z" horiz-adv-x="857.1" />
+<glyph glyph-name="doc-text" unicode="&#x1f5c8;" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
+<glyph glyph-name="trashcan" unicode="&#x1f5d1;" d="m688 725h-250c0 0 0 24 0 31 0 18-14 32-32 32s-31-14-31-32c0-17 0-31 0-31h-250c-34 0-62-28-62-62v-63c0-34 28-62 62-62v-563c0-35 28-63 63-63h437c35 0 63 28 63 63v563c34 0 62 28 62 62v63c0 34-28 62-62 62z m-63-719c0-17-14-31-31-31h-375c-17 0-31 14-31 31v532h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62v-469c0-17 14-31 31-31s32 14 32 31l0 469h62l0-469c0-17 14-31 31-31s32 14 32 31v469h62v-532z m63 610c0-9-7-16-16-16h-531c-9 0-16 7-16 16v31c0 9 7 16 16 16h531c9 0 16-7 16-16v-31z" horiz-adv-x="750" />
+<glyph glyph-name="arrows-cw" unicode="&#x1f5d8;" d="m843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-37 90-57t105-20q74 0 139 37t104 99q6 10 29 66 5 13 17 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
+<glyph glyph-name="comment" unicode="&#x1f5e9;" d="m750 725h-625c-60 0-125-62-125-125v-375c0-125 125-125 125-125h63v-250l250 250c0 0 252 0 312 0s125 66 125 125v375c0 61-62 125-125 125z" horiz-adv-x="875" />
+<glyph glyph-name="comment-discussion" unicode="&#x1f5ea;" d="m250 350c0 63 0 188 0 188s-156 0-187 0-63-32-63-63 0-281 0-312 31-63 63-63 62 0 62 0v-188l190 188s158 0 187 0 61 31 61 63 0 62 0 62-125 0-188 0-125 63-125 125z m563 375c-32 0-407 0-438 0s-62-31-62-62 0-282 0-313 31-62 62-62 186 0 186 0l189-188v188s31 0 63 0 62 31 62 62 0 281 0 313-31 62-62 62z" horiz-adv-x="875" />
+<glyph glyph-name="cancel" unicode="&#x1f5f4;" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="cancel-circled" unicode="&#x1f5f5;" d="m641 224q0 14-10 25l-101 101 101 101q10 11 10 25 0 15-10 26l-51 50q-10 11-25 11-15 0-25-11l-101-101-101 101q-11 11-26 11-15 0-25-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 25-11 15 0 26 11l101 101 101-101q10-11 25-11 15 0 25 11l51 50q10 11 10 26z m216 126q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="ok" unicode="&#x1f5f8;" d="m932 534q0-22-15-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q15-16 15-38z" horiz-adv-x="1000" />
+<glyph glyph-name="ok-circled" unicode="&#x1f5f9;" d="m717 440q0 16-11 26l-50 50q-11 11-25 11t-26-11l-227-227-126 126q-11 11-25 11t-26-11l-50-50q-10-10-10-26 0-15 10-25l202-202q10-10 25-10 15 0 25 10l303 303q11 10 11 25z m140-90q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+<glyph glyph-name="block" unicode="&#x1f6ab;" d="m732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-112-41-152q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
+<glyph glyph-name="tools" unicode="&#x1f6e0;" d="m280 396c16-16 80-83 80-83l35 36-55 57 105 112c0 0-47 47-26 28 20 74 1 157-55 215-56 58-135 77-206 57l120-125-31-122-119-33-120 125c-20-74-1-156 55-214 58-60 143-78 217-53z m402-121l-145-144 240-249c20-20 45-31 71-31 26 0 52 11 71 31 40 40 40 106 0 147l-237 246z m318 417l-153 158-451-466 55-57-270-279-62-33-87-142 23-23 137 90 32 64 270 279 55-57 451 466z" horiz-adv-x="1000" />
 </font>
 </defs>
 </svg>
\ No newline at end of file
Binary file kallithea/public/fontello/font/kallithea.ttf has changed
Binary file kallithea/public/fontello/font/kallithea.woff has changed
--- a/kallithea/templates/admin/auth/auth_settings.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/admin/auth/auth_settings.html	Thu May 07 13:30:17 2015 +0200
@@ -58,7 +58,7 @@
             <div class="field">
                 <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
                 <div class="input">
-                    ${h.password(fullsetting,class_='small',autocomplete="off")}
+                    ${h.password(fullsetting,class_='small')}
                     <span class="help-block">${setting["description"]}</span>
                 </div>
             </div>
--- a/kallithea/templates/admin/my_account/my_account_password.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/admin/my_account/my_account_password.html	Thu May 07 13:30:17 2015 +0200
@@ -7,7 +7,7 @@
             <label for="current_password">${_('Current password')}:</label>
         </div>
         <div class="input">
-            ${h.password('current_password',class_='medium',autocomplete="off")}
+            ${h.password('current_password',class_='medium')}
         </div>
      </div>
 
@@ -16,7 +16,7 @@
             <label for="new_password">${_('New password')}:</label>
         </div>
         <div class="input">
-            ${h.password('new_password',class_='medium', autocomplete="off")}
+            ${h.password('new_password',class_='medium')}
         </div>
      </div>
 
@@ -25,7 +25,7 @@
             <label for="password_confirmation">${_('Confirm new password')}:</label>
         </div>
         <div class="input">
-            ${h.password('new_password_confirmation',class_='medium', autocomplete="off")}
+            ${h.password('new_password_confirmation',class_='medium')}
         </div>
      </div>
 
--- a/kallithea/templates/admin/users/user_add.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/admin/users/user_add.html	Thu May 07 13:30:17 2015 +0200
@@ -41,7 +41,7 @@
                     <label for="password">${_('Password')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('password',class_='small',autocomplete="off")}
+                    ${h.password('password',class_='small')}
                 </div>
              </div>
 
@@ -50,7 +50,7 @@
                     <label for="password_confirmation">${_('Password confirmation')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('password_confirmation',class_="small",autocomplete="off")}
+                    ${h.password('password_confirmation',class_="small")}
                 </div>
              </div>
 
--- a/kallithea/templates/admin/users/user_edit.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit.html	Thu May 07 13:30:17 2015 +0200
@@ -29,7 +29,7 @@
         <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', id=c.user.user_id)}">${_('Profile')}</a></li>
         <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('edit_user_api_keys', id=c.user.user_id)}">${_('API Keys')}</a></li>
         <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', id=c.user.user_id)}">${_('Advanced')}</a></li>
-        <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Default Permissions')}</a></li>
+        <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_perms', id=c.user.user_id)}">${_('Permissions')}</a></li>
         <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
         <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', id=c.user.user_id)}">${_('IP Whitelist')}</a></li>
       </ul>
--- a/kallithea/templates/admin/users/user_edit_profile.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/admin/users/user_edit_profile.html	Thu May 07 13:30:17 2015 +0200
@@ -69,7 +69,7 @@
                     <label for="new_password">${_('New password')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('new_password',class_='medium%s' % disabled,autocomplete="off",readonly=readonly)}
+                    ${h.password('new_password',class_='medium%s' % disabled,readonly=readonly)}
                 </div>
              </div>
 
@@ -78,7 +78,7 @@
                     <label for="password_confirmation">${_('New password confirmation')}:</label>
                 </div>
                 <div class="input">
-                    ${h.password('password_confirmation',class_="medium%s" % disabled,autocomplete="off",readonly=readonly)}
+                    ${h.password('password_confirmation',class_="medium%s" % disabled,readonly=readonly)}
                 </div>
              </div>
 
--- a/kallithea/templates/base/perms_summary.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/base/perms_summary.html	Thu May 07 13:30:17 2015 +0200
@@ -11,8 +11,8 @@
             %if section != 'global':
                 <div style="float: right">
                 ${_('show')}:
-                ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')}   <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
-                ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')}   <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
+                ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
+                ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
                 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
                 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
                 </div>
@@ -108,15 +108,12 @@
                 $('#empty_{0}'.format(section)).hide();
             }
         }
-        $('.perm_filter').on('change', function(e){
-            var self = this;
-            var section = $(this).attr('section');
+        var update_show = function($checkbox){
+            var section = $checkbox.attr('section');
 
-            var opts = {}
             var elems = $('.filter_' + section).each(function(el){
-                var perm_type = $(this).attr('perm_type');
-                var checked = this.checked;
-                opts[perm_type] = checked;
+                var perm_type = $checkbox.attr('perm_type');
+                var checked = $checkbox.checked;
                 if(checked){
                     $('.'+section+'_'+perm_type).show();
                 }
@@ -125,8 +122,9 @@
                 }
             });
             show_empty(section);
-        })
-
+        }
+        $('.perm_filter').on('change', function(){update_show($(this));});
+        $('.perm_filter[value=none]').each(function(){this.checked = false; update_show($(this));});
     })
 </script>
 </%def>
--- a/kallithea/templates/changelog/changelog.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/changelog/changelog.html	Thu May 07 13:30:17 2015 +0200
@@ -89,11 +89,11 @@
                           %if c.statuses.get(cs.raw_id):
                             <div class="changeset-status-ico">
                             %if c.statuses.get(cs.raw_id)[2]:
-                              <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request #%s') % (h.changeset_status_lbl(c.statuses.get(cs.raw_id)[0]), c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
+                              <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
                                 <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                               </a>
                             %else:
-                              <a class="tooltip" title="${_('Changeset status: %s') % h.changeset_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
+                              <a class="tooltip" title="${_('Changeset status: %s') % c.statuses.get(cs.raw_id)[1]}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
                                   <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                               </a>
                             %endif
--- a/kallithea/templates/changelog/changelog_summary_data.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/changelog/changelog_summary_data.html	Thu May 07 13:30:17 2015 +0200
@@ -17,7 +17,7 @@
               %if c.statuses.get(cs.raw_id):
                 <div class="changeset-status-ico shortlog">
                 %if c.statuses.get(cs.raw_id)[2]:
-                  <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
+                  <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
                     <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
                   </a>
                 %else:
--- a/kallithea/templates/changeset/changeset_file_comment.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/changeset/changeset_file_comment.html	Thu May 07 13:30:17 2015 +0200
@@ -11,7 +11,7 @@
                ${h.gravatar(co.author.email, size=20)}
           </div>
           <div class="user">
-              ${co.author.username}
+              ${co.author.username_and_name}
           </div>
           <div class="date">
               ${h.age(co.modified_at)}
@@ -51,7 +51,13 @@
       %endif
       </div>
       <div class="text">
+        %if co.text:
           ${h.rst_w_mentions(co.text)|n}
+        %else:
+          <div class="rst-block automatic-comment">
+            <p>${_('No comments.')}</p>
+          </div>
+        %endif
       </div>
     </div>
   </div>
--- a/kallithea/templates/files/files_browser.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/files/files_browser.html	Thu May 07 13:30:17 2015 +0200
@@ -47,7 +47,7 @@
             <div>
                 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
                 <div id="node_filter_box" style="display:none">
-                ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
+                ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter">
                 </div>
             </div>
         </div>
--- a/kallithea/templates/pullrequests/pullrequest_show.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/pullrequests/pullrequest_show.html	Thu May 07 13:30:17 2015 +0200
@@ -3,11 +3,11 @@
 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
 <%block name="title">
-    ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
+    ${_('%s Pull Request %s') % (c.repo_name, c.pull_request.nice_id())}
 </%block>
 
 <%def name="breadcrumbs_links()">
-    ${_('Pull request #%s from %s#%s') % (c.pull_request.pull_request_id, c.pull_request.org_repo.repo_name, c.cs_branch_name)}
+    ${_('Pull request %s from %s#%s') % (c.pull_request.nice_id(), c.pull_request.org_repo.repo_name, c.cs_branch_name)}
 </%def>
 
 <%block name="header_menu">
--- a/kallithea/templates/register.html	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/templates/register.html	Thu May 07 13:30:17 2015 +0200
@@ -33,7 +33,7 @@
                         <label for="password">${_('Password')}:</label>
                     </div>
                     <div class="input">
-                        ${h.password('password',class_="medium",autocomplete="off")}
+                        ${h.password('password',class_="medium")}
                     </div>
                 </div>
 
@@ -42,7 +42,7 @@
                         <label for="password">${_('Re-enter password')}:</label>
                     </div>
                     <div class="input">
-                        ${h.password('password_confirmation',class_="medium",autocomplete="off")}
+                        ${h.password('password_confirmation',class_="medium")}
                     </div>
                 </div>
 
--- a/kallithea/tests/__init__.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/__init__.py	Thu May 07 13:30:17 2015 +0200
@@ -55,7 +55,7 @@
 from kallithea.lib.compat import unittest
 from kallithea import is_windows
 from kallithea.model.db import User
-from kallithea.tests.nose_parametrized import parameterized
+from kallithea.tests.parameterized import parameterized
 from kallithea.lib.utils2 import safe_str
 
 
--- a/kallithea/tests/api/api_base.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/api/api_base.py	Thu May 07 13:30:17 2015 +0200
@@ -88,7 +88,7 @@
     return gr
 
 
-class BaseTestApi(object):
+class _BaseTestApi(object):
     REPO = None
     REPO_TYPE = None
 
--- a/kallithea/tests/api/test_api_git.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/api/test_api_git.py	Thu May 07 13:30:17 2015 +0200
@@ -12,10 +12,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests import *
-from kallithea.tests.api.api_base import BaseTestApi
+from kallithea.tests import TestController, GIT_REPO
+from kallithea.tests.api.api_base import _BaseTestApi
 
 
-class TestGitApi(BaseTestApi, TestController):
+class TestGitApi(_BaseTestApi, TestController):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
--- a/kallithea/tests/api/test_api_hg.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/api/test_api_hg.py	Thu May 07 13:30:17 2015 +0200
@@ -12,10 +12,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests import *
-from kallithea.tests.api.api_base import BaseTestApi
+from kallithea.tests import TestController, HG_REPO
+from kallithea.tests.api.api_base import _BaseTestApi
 
 
-class TestHgApi(BaseTestApi, TestController):
+class TestHgApi(_BaseTestApi, TestController):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/conftest.py	Thu May 07 13:30:17 2015 +0200
@@ -0,0 +1,29 @@
+import os
+import sys
+
+import pkg_resources
+from paste.deploy import loadapp
+import pylons.test
+from pylons.i18n.translation import _get_translator
+
+
+def pytest_configure():
+    path = os.getcwd()
+    sys.path.insert(0, path)
+    pkg_resources.working_set.add_entry(path)
+    pylons.test.pylonsapp = loadapp('config:test.ini', relative_to=path)
+
+    # Setup the config and app_globals, only works if we can get
+    # to the config object
+    conf = getattr(pylons.test.pylonsapp, 'config')
+    if conf:
+        pylons.config._push_object(conf)
+
+        if 'pylons.app_globals' in conf:
+            pylons.app_globals._push_object(conf['pylons.app_globals'])
+
+    # Initialize a translator for tests that utilize i18n
+    translator = _get_translator(pylons.config.get('lang'))
+    pylons.translator._push_object(translator)
+
+    return pylons.test.pylonsapp
--- a/kallithea/tests/fixtures/journal_dump.csv	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/fixtures/journal_dump.csv	Thu May 07 13:30:17 2015 +0200
@@ -1782,9 +1782,9 @@
 1846,3,demo,99,another-fork-to-check-code-review,"",user_commented_revision:fba17a64fa4978bfea19222da5e64a18cfddeecd,2012-11-06 04:15:29.032768
 1847,3,demo,68,aaa-project,"",user_commented_revision:0c33fa58efc5a5541d8d3f1a3d3b77367e3d94f5,2012-11-06 04:17:16.091927
 1848,3,demo,249,abcdefg,"",user_commented_revision:5d80e28538141e322b317168e2367fb03178d58c,2012-11-06 06:10:53.372505
-1849,3,demo,362,Marcin,"",started_following_repo,2012-11-06 10:45:38.505485
-1850,3,demo,362,Marcin,"",user_created_repo,2012-11-06 10:45:38.518969
-1851,3,demo,362,hidden/Marcin,"",user_updated_repo,2012-11-06 10:45:46.281581
+1849,3,demo,362,Username,"",started_following_repo,2012-11-06 10:45:38.505485
+1850,3,demo,362,Username,"",user_created_repo,2012-11-06 10:45:38.518969
+1851,3,demo,362,hidden/Username,"",user_updated_repo,2012-11-06 10:45:46.281581
 1852,3,demo,177,blah,62.200.22.2,push:b369fb18c8d61fe0d3b14c417466680230cabe46,2012-11-06 10:47:55.655029
 1853,3,demo,99,another-fork-to-check-code-review,"",user_commented_revision:d5422faf648cc589425cd3b0dbf1f6dbf93036a0,2012-11-06 13:12:05.517155
 1854,3,demo,38,code-review-test,"",user_commented_revision:6d7db5794e8cad7da042b6ae6238116c6e59a4d2,2012-11-06 16:12:59.38977
--- a/kallithea/tests/functional/test_admin_repos.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/functional/test_admin_repos.py	Thu May 07 13:30:17 2015 +0200
@@ -26,7 +26,7 @@
     return perm
 
 
-class _BaseTest(TestController):
+class _BaseTest(object):
     """
     Write all tests here
     """
@@ -638,7 +638,7 @@
         # repo must not be in filesystem !
         self.assertFalse(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)))
 
-class TestAdminReposControllerGIT(_BaseTest):
+class TestAdminReposControllerGIT(TestController, _BaseTest):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
     NEW_REPO = NEW_GIT_REPO
@@ -646,7 +646,7 @@
     OTHER_TYPE = 'hg'
 
 
-class TestAdminReposControllerHG(_BaseTest):
+class TestAdminReposControllerHG(TestController, _BaseTest):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
     NEW_REPO = NEW_HG_REPO
--- a/kallithea/tests/functional/test_forks.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/functional/test_forks.py	Thu May 07 13:30:17 2015 +0200
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+
+import unittest
+
 from kallithea.tests import *
 from kallithea.tests.fixture import Fixture
 
@@ -12,15 +15,7 @@
 from kallithea.tests import *
 
 
-class _BaseTest(TestController):
-    """
-    Write all tests here
-    """
-    REPO = None
-    REPO_TYPE = None
-    NEW_REPO = None
-    REPO_FORK = None
-
+class _BaseFixture(unittest.TestCase):
     @classmethod
     def setup_class(cls):
         pass
@@ -40,6 +35,16 @@
         Session().delete(self.u1)
         Session().commit()
 
+
+class _BaseTestCase(object):
+    """
+    Write all tests here
+    """
+    REPO = None
+    REPO_TYPE = None
+    NEW_REPO = None
+    REPO_FORK = None
+
     def test_index(self):
         self.log_user()
         repo_name = self.REPO
@@ -218,14 +223,14 @@
         response.mustcontain('There are no forks yet')
 
 
-class TestGIT(_BaseTest):
+class TestGIT(TestController, _BaseTestCase, _BaseFixture):
     REPO = GIT_REPO
     NEW_REPO = NEW_GIT_REPO
     REPO_TYPE = 'git'
     REPO_FORK = GIT_FORK
 
 
-class TestHG(_BaseTest):
+class TestHG(TestController, _BaseTestCase, _BaseFixture):
     REPO = HG_REPO
     NEW_REPO = NEW_HG_REPO
     REPO_TYPE = 'hg'
--- a/kallithea/tests/functional/test_login.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/functional/test_login.py	Thu May 07 13:30:17 2015 +0200
@@ -208,7 +208,7 @@
     def test_register_ok(self):
         username = 'test_regular4'
         password = 'qweqwe'
-        email = 'marcin@test.com'
+        email = 'username@test.com'
         name = 'testname'
         lastname = 'testlastname'
 
@@ -233,7 +233,7 @@
         self.assertEqual(ret.admin, False)
 
     def test_forgot_password_wrong_mail(self):
-        bad_email = 'marcin@wrongmail.org'
+        bad_email = 'username@wrongmail.org'
         response = self.app.post(
                         url(controller='login', action='password_reset'),
                             {'email': bad_email, }
@@ -250,7 +250,7 @@
 
         username = 'test_password_reset_1'
         password = 'qweqwe'
-        email = 'marcin@python-works.com'
+        email = 'username@python-works.com'
         name = 'passwd'
         lastname = 'reset'
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/models/test_changeset_status.py	Thu May 07 13:30:17 2015 +0200
@@ -0,0 +1,40 @@
+from kallithea.tests import *
+from kallithea.model.changeset_status import ChangesetStatusModel
+from kallithea.model.db import ChangesetStatus as CS
+
+class CSM(object): # ChangesetStatusMock
+
+    def __init__(self, status):
+        self.status = status
+
+class TestChangesetStatusCalculation(BaseTestCase):
+
+    def setUp(self):
+        self.m = ChangesetStatusModel()
+
+    @parameterized.expand([
+        ('empty list', CS.STATUS_UNDER_REVIEW, []),
+        ('approve', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED)]),
+        ('approve2', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED)]),
+        ('approve_reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED)]),
+        ('approve_underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW)]),
+        ('approve_notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED)]),
+        ('underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW)]),
+        ('reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED)]),
+        ('reject_underreview', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW)]),
+        ('reject_notreviewed', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED)]),
+        ('notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED)]),
+        ('approve_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), None]),
+        ('approve2_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED), None]),
+        ('approve_reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED), None]),
+        ('approve_underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW), None]),
+        ('approve_notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED), None]),
+        ('underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW), None]),
+        ('reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), None]),
+        ('reject_underreview_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW), None]),
+        ('reject_notreviewed_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED), None]),
+        ('notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED), None]),
+    ])
+    def test_result(self, name, expected_result, statuses):
+        result = self.m._calculate_status(statuses)
+        self.assertEqual(result, expected_result)
--- a/kallithea/tests/models/test_user_group_permissions_on_repo_groups.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/models/test_user_group_permissions_on_repo_groups.py	Thu May 07 13:30:17 2015 +0200
@@ -1,11 +1,9 @@
 import functools
-from kallithea.tests import *
 
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.db import RepoGroup
 
 from kallithea.model.meta import Session
-from nose.tools import with_setup
 from kallithea.tests.models.common import _create_project_tree, check_tree_perms, \
     _get_perms, _check_expected_count, expected_count, _destroy_project_tree
 from kallithea.model.user_group import UserGroupModel
@@ -26,6 +24,13 @@
     repo_group = RepoGroup.get_by_group_name(group_name=group_name)
     if not repo_group:
         raise Exception('Cannot get group %s' % group_name)
+
+    # Start with a baseline that current group can read recursive
+    perms_updates = [[test_u2_gr_id, 'group.read', 'users_group']]
+    RepoGroupModel()._update_permissions(repo_group,
+                                         perms_updates=perms_updates,
+                                         recursive='all', check_perms=False)
+
     perms_updates = [[test_u2_gr_id, perm, 'users_group']]
     RepoGroupModel()._update_permissions(repo_group,
                                          perms_updates=perms_updates,
@@ -53,9 +58,9 @@
 
 def teardown_module():
     _destroy_project_tree(test_u2_id)
+    fixture.destroy_user_group('perms_group_1')
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -75,7 +80,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode_subgroup():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -95,7 +99,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode():
 
     # set permission to g0 recursive mode, all children including
@@ -115,7 +118,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_inner_group():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -133,7 +135,6 @@
         yield check_tree_perms, name, perm, group, 'group.none'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_deepest():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -151,7 +152,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -168,7 +168,7 @@
     for name, perm in items:
         yield check_tree_perms, name, perm, group, 'group.admin'
 
-@with_setup(permissions_setup_func)
+
 def test_user_permissions_on_group_with_recursive_mode_on_repos():
     # set permission to g0/g0_1 with recursive mode on just repositories
     recursive = 'repos'
@@ -192,7 +192,7 @@
             old_perm = perm
         yield check_tree_perms, name, perm, group, old_perm
 
-@with_setup(permissions_setup_func)
+
 def test_user_permissions_on_group_with_recursive_mode_on_repo_groups():
     # set permission to g0/g0_1 with recursive mode on just repository groups
     recursive = 'groups'
--- a/kallithea/tests/models/test_user_groups.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/models/test_user_groups.py	Thu May 07 13:30:17 2015 +0200
@@ -1,6 +1,6 @@
 from kallithea.model.db import User
 
-from kallithea.tests import *
+from kallithea.tests import BaseTestCase, parameterized, TEST_USER_REGULAR_LOGIN
 from kallithea.tests.fixture import Fixture
 
 from kallithea.model.user_group import UserGroupModel
@@ -18,7 +18,6 @@
             fixture.destroy_user_group(gr)
         Session().commit()
 
-
     @parameterized.expand([
         ([], [], [], [], []),
         ([], ['regular'], [], [], ['regular']),  # no changes of regular
--- a/kallithea/tests/models/test_user_permissions_on_repo_groups.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/models/test_user_permissions_on_repo_groups.py	Thu May 07 13:30:17 2015 +0200
@@ -1,11 +1,9 @@
 import functools
-from kallithea.tests import *
 
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.db import RepoGroup, User
 
 from kallithea.model.meta import Session
-from nose.tools import with_setup
 from kallithea.tests.models.common import _create_project_tree, check_tree_perms, \
     _get_perms, _check_expected_count, expected_count, _destroy_project_tree
 
@@ -22,7 +20,6 @@
     """
     if not user_id:
         user_id = test_u1_id
-        # called by the @with_setup decorator also reset the default user stuff
         permissions_setup_func(group_name, perm, recursive,
                                user_id=User.get_default_user().user_id)
 
@@ -30,12 +27,19 @@
     if not repo_group:
         raise Exception('Cannot get group %s' % group_name)
 
+    # Start with a baseline that current group can read recursive
+    perms_updates = [[user_id, 'group.read', 'user']]
+    RepoGroupModel()._update_permissions(repo_group,
+                                         perms_updates=perms_updates,
+                                         recursive='all', check_perms=False)
+
     perms_updates = [[user_id, perm, 'user']]
     RepoGroupModel()._update_permissions(repo_group,
                                          perms_updates=perms_updates,
                                          recursive=recursive, check_perms=False)
     Session().commit()
 
+
 def setup_module():
     global test_u1_id, _get_repo_perms, _get_group_perms
     test_u1 = _create_project_tree()
@@ -51,7 +55,6 @@
     _destroy_project_tree(test_u1_id)
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -71,7 +74,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_without_recursive_mode_subgroup():
     # set permission to g0 non-recursive mode
     recursive = 'none'
@@ -91,7 +93,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode():
 
     # set permission to g0 recursive mode, all children including
@@ -111,7 +112,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_for_default_user():
 
     # set permission to g0 recursive mode, all children including
@@ -139,7 +139,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_inner_group():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -157,7 +156,6 @@
         yield check_tree_perms, name, perm, group, 'group.none'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_deepest():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -175,7 +173,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
     ## set permission to g0_3 group to none
     recursive = 'all'
@@ -193,7 +190,6 @@
         yield check_tree_perms, name, perm, group, 'group.admin'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_repo_mode_for_default_user():
     # set permission to g0/g0_1 recursive repos only mode, all children including
     # other repos should have this permission now set, inner groups are excluded!
@@ -228,7 +224,6 @@
         yield check_tree_perms, name, perm, group, old_perm
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_repo_mode_inner_group():
     ## set permission to g0_3 group to none, with recursive repos only
     recursive = 'repos'
@@ -253,7 +248,6 @@
         yield check_tree_perms, name, perm, group, old_perm
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_group_mode_for_default_user():
     # set permission to g0/g0_1 with recursive groups only mode, all children including
     # other groups should have this permission now set. repositories should
@@ -281,7 +275,6 @@
         yield check_tree_perms, name, perm, group, 'group.write'
 
 
-@with_setup(permissions_setup_func)
 def test_user_permissions_on_group_with_recursive_group_mode_inner_group():
     ## set permission to g0_3 group to none, with recursive mode for groups only
     recursive = 'groups'
--- a/kallithea/tests/nose_parametrized.py	Thu May 07 13:29:49 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-import re
-import new
-import inspect
-import logging
-import logging.handlers
-from functools import wraps
-
-from nose.tools import nottest
-from unittest import TestCase
-
-
-def _terrible_magic_get_defining_classes():
-    """ Returns the set of parent classes of the class currently being defined.
-        Will likely only work if called from the ``parameterized`` decorator.
-        This function is entirely @brandon_rhodes's fault, as he suggested
-        the implementation: http://stackoverflow.com/a/8793684/71522
-        """
-    stack = inspect.stack()
-    if len(stack) <= 4:
-        return []
-    frame = stack[3]
-    code_context = frame[4][0].strip()
-    if not code_context.startswith("class "):
-        return []
-    _, parents = code_context.split("(", 1)
-    parents, _ = parents.rsplit(")", 1)
-    return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
-
-
-def parameterized(input):
-    """ Parameterize a test case:
-        >>> add1_tests = [(1, 2), (2, 3)]
-        >>> class TestFoo(object):
-        ...     @parameterized(add1_tests)
-        ...     def test_add1(self, input, expected):
-        ...         assert_equal(add1(input), expected)
-        >>> @parameterized(add1_tests)
-        ... def test_add1(input, expected):
-        ...     assert_equal(add1(input), expected)
-        >>>
-        """
-
-    if not hasattr(input, "__iter__"):
-        raise ValueError("expected iterable input; got %r" % (input,))
-
-    def parameterized_helper(f):
-        attached_instance_method = [False]
-
-        parent_classes = _terrible_magic_get_defining_classes()
-        if any(issubclass(cls, TestCase) for cls in parent_classes):
-            raise Exception("Warning: '@parameterized' tests won't work "
-                            "inside subclasses of 'TestCase' - use "
-                            "'@parameterized.expand' instead")
-
-        @wraps(f)
-        def parameterized_helper_method(self=None):
-            if self is not None and not attached_instance_method[0]:
-                # confusingly, we need to create a named instance method and
-                # attach that to the class...
-                cls = self.__class__
-                im_f = new.instancemethod(f, None, cls)
-                setattr(cls, f.__name__, im_f)
-                attached_instance_method[0] = True
-            for args in input:
-                if isinstance(args, basestring):
-                    args = [args]
-                # ... then pull that named instance method off, turning it into
-                # a bound method ...
-                if self is not None:
-                    args = [getattr(self, f.__name__)] + list(args)
-                else:
-                    args = [f] + list(args)
-                # ... then yield that as a tuple. If those steps aren't
-                # followed precicely, Nose gets upset and doesn't run the test
-                # or doesn't run setup methods.
-                yield tuple(args)
-
-        f.__name__ = "_helper_for_%s" % (f.__name__,)
-        parameterized_helper_method.parameterized_input = input
-        parameterized_helper_method.parameterized_func = f
-        return parameterized_helper_method
-
-    return parameterized_helper
-
-
-def to_safe_name(s):
-    return re.sub("[^a-zA-Z0-9_]", "", s)
-
-
-def parameterized_expand_helper(func_name, func, args):
-    def parameterized_expand_helper_helper(self=()):
-        if self != ():
-            self = (self,)
-        return func(*(self + args))
-    parameterized_expand_helper_helper.__name__ = str(func_name)
-    return parameterized_expand_helper_helper
-
-
-def parameterized_expand(input):
-    """ A "brute force" method of parameterizing test cases. Creates new test
-        cases and injects them into the namespace that the wrapped function
-        is being defined in. Useful for parameterizing tests in subclasses
-        of 'UnitTest', where Nose test generators don't work.
-
-        >>> @parameterized.expand([("foo", 1, 2)])
-        ... def test_add1(name, input, expected):
-        ...     actual = add1(input)
-        ...     assert_equal(actual, expected)
-        ...
-        >>> locals()
-        ... 'test_add1_foo_0': <function ...> ...
-        >>>
-        """
-
-    def parameterized_expand_wrapper(f):
-        stack = inspect.stack()
-        frame = stack[1]
-        frame_locals = frame[0].f_locals
-
-        base_name = f.__name__
-        for num, args in enumerate(input):
-            name_suffix = "_%s" % (num,)
-            if len(args) > 0 and isinstance(args[0], basestring):
-                name_suffix += "_" + to_safe_name(args[0])
-            name = base_name + name_suffix
-            new_func = parameterized_expand_helper(name, f, args)
-            frame_locals[name] = new_func
-        return nottest(f)
-    return parameterized_expand_wrapper
-
-parameterized.expand = parameterized_expand
-
-
-def assert_contains(haystack, needle):
-    if needle not in haystack:
-        raise AssertionError("%r not in %r" % (needle, haystack))
-
-
-def assert_not_contains(haystack, needle):
-    if needle in haystack:
-        raise AssertionError("%r in %r" % (needle, haystack))
-
-
-def imported_from_test():
-    """ Returns true if it looks like this module is being imported by unittest
-        or nose. """
-    import re
-    import inspect
-    nose_re = re.compile(r"\bnose\b")
-    unittest_re = re.compile(r"\bunittest2?\b")
-    for frame in inspect.stack():
-        file = frame[1]
-        if nose_re.search(file) or unittest_re.search(file):
-            return True
-    return False
-
-
-def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
-    try:
-        func()
-    except exc_type, e:
-        if str_contains is not None and str_contains not in str(e):
-            raise AssertionError("%s raised, but %r does not contain %r"
-                                 % (exc_type, str(e), str_contains))
-        if repr_contains is not None and repr_contains not in repr(e):
-            raise AssertionError("%s raised, but %r does not contain %r"
-                                 % (exc_type, repr(e), repr_contains))
-        return e
-    else:
-        raise AssertionError("%s not raised" % (exc_type,))
-
-
-log_handler = None
-
-
-def setup_logging():
-    """ Configures a log handler which will capure log messages during a test.
-        The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
-        used to make assertions about these logged messages.
-
-        For example::
-
-            from ensi_common.testing import (
-                setup_logging, teardown_logging, assert_no_errors_logged,
-                assert_logged,
-            )
-
-            class TestWidget(object):
-                def setup(self):
-                    setup_logging()
-
-                def teardown(self):
-                    assert_no_errors_logged()
-                    teardown_logging()
-
-                def test_that_will_fail(self):
-                    log.warning("this warning message will trigger a failure")
-
-                def test_that_will_pass(self):
-                    log.info("but info messages are ok")
-                    assert_logged("info messages are ok")
-        """
-
-    global log_handler
-    if log_handler is not None:
-        logging.getLogger().removeHandler(log_handler)
-    log_handler = logging.handlers.BufferingHandler(1000)
-    formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
-    log_handler.setFormatter(formatter)
-    logging.getLogger().addHandler(log_handler)
-
-
-def teardown_logging():
-    global log_handler
-    if log_handler is not None:
-        logging.getLogger().removeHandler(log_handler)
-        log_handler = None
-
-
-def logged_messages():
-    assert log_handler, "setup_logging not called"
-    return [(log_handler.format(record), record) for record in log_handler.buffer]
-
-
-def assert_no_errors_logged():
-    for _, record in logged_messages():
-        if record.levelno >= logging.WARNING:
-            # Assume that the nose log capture plugin is being used, so it will
-            # show the exception.
-            raise AssertionError("an unexpected error was logged")
-
-
-def assert_logged(expected_msg_contents):
-    for msg, _ in logged_messages():
-        if expected_msg_contents in msg:
-            return
-    raise AssertionError("no logged message contains %r"
-                         % (expected_msg_contents,))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/other/manual_test_vcs_operations.py	Thu May 07 13:30:17 2015 +0200
@@ -0,0 +1,536 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.tests.test_scm_operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Test suite for making push/pull operations.
+
+Run it in two terminals::
+ paster serve test.ini
+ KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 nosetests kallithea/tests/other/manual_test_vcs_operations.py
+
+You must have git > 1.8.1 for tests to work fine
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Dec 30, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+
+"""
+
+import tempfile
+import time
+from os.path import join as jn
+
+from tempfile import _RandomNameSequence
+from subprocess import Popen, PIPE
+
+from kallithea.tests import *
+from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation
+from kallithea.model.meta import Session
+from kallithea.model.repo import RepoModel
+from kallithea.model.user import UserModel
+
+DEBUG = True
+HOST = '127.0.0.1:5000'  # test host
+
+
+class Command(object):
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def execute(self, cmd, *args):
+        """
+        Runs command on the system with given ``args``.
+        """
+
+        command = cmd + ' ' + ' '.join(args)
+        if DEBUG:
+            print '*** CMD %s ***' % command
+        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
+        stdout, stderr = p.communicate()
+        if DEBUG:
+            print 'stdout:', repr(stdout)
+            print 'stderr:', repr(stderr)
+        return stdout, stderr
+
+
+def _get_tmp_dir():
+    return tempfile.mkdtemp(prefix='rc_integration_test')
+
+
+def _construct_url(repo, dest=None, **kwargs):
+    if dest is None:
+        #make temp clone
+        dest = _get_tmp_dir()
+    params = {
+        'user': TEST_USER_ADMIN_LOGIN,
+        'passwd': TEST_USER_ADMIN_PASS,
+        'host': HOST,
+        'cloned_repo': repo,
+        'dest': dest
+    }
+    params.update(**kwargs)
+    if params['user'] and params['passwd']:
+        _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s %(dest)s' % params
+    else:
+        _url = 'http://(host)s/%(cloned_repo)s %(dest)s' % params
+    return _url
+
+
+def _add_files_and_push(vcs, DEST, **kwargs):
+    """
+    Generate some files, add it to DEST repo and push back
+    vcs is git or hg and defines what VCS we want to make those files for
+
+    :param vcs:
+    :param DEST:
+    """
+    # commit some stuff into this repo
+    cwd = path = jn(DEST)
+    #added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
+    added_file = jn(path, '%ssetup.py' % _RandomNameSequence().next())
+    Command(cwd).execute('touch %s' % added_file)
+    Command(cwd).execute('%s add %s' % (vcs, added_file))
+
+    for i in xrange(kwargs.get('files_no', 3)):
+        cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
+        Command(cwd).execute(cmd)
+        author_str = 'User ǝɯɐᴎ <me@email.com>'
+        if vcs == 'hg':
+            cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
+                i, author_str, added_file
+            )
+        elif vcs == 'git':
+            cmd = """EMAIL="me@email.com" git commit -m 'commited new %s' --author '%s' %s """ % (
+                i, author_str, added_file
+            )
+        Command(cwd).execute(cmd)
+
+    # PUSH it back
+    _REPO = None
+    if vcs == 'hg':
+        _REPO = HG_REPO
+    elif vcs == 'git':
+        _REPO = GIT_REPO
+
+    kwargs['dest'] = ''
+    clone_url = _construct_url(_REPO, **kwargs)
+    if 'clone_url' in kwargs:
+        clone_url = kwargs['clone_url']
+    stdout = stderr = None
+    if vcs == 'hg':
+        stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url)
+    elif vcs == 'git':
+        stdout, stderr = Command(cwd).execute('git push --verbose', clone_url + " master")
+
+    return stdout, stderr
+
+
+def set_anonymous_access(enable=True):
+    user = User.get_by_username(User.DEFAULT_USER)
+    user.active = enable
+    Session().add(user)
+    Session().commit()
+    print '\tanonymous access is now:', enable
+    if enable != User.get_by_username(User.DEFAULT_USER).active:
+        raise Exception('Cannot set anonymous access')
+
+
+#==============================================================================
+# TESTS
+#==============================================================================
+
+
+def _check_proper_git_push(stdout, stderr):
+    #WTF Git stderr is output ?!
+    assert 'fatal' not in stderr
+    assert 'rejected' not in stderr
+    assert 'Pushing to' in stderr
+    assert 'master -> master' in stderr
+
+
+class TestVCSOperations(BaseTestCase):
+
+    @classmethod
+    def setup_class(cls):
+        #DISABLE ANONYMOUS ACCESS
+        set_anonymous_access(False)
+
+    def setUp(self):
+        r = Repository.get_by_repo_name(GIT_REPO)
+        Repository.unlock(r)
+        r.enable_locking = False
+        Session().add(r)
+        Session().commit()
+
+        r = Repository.get_by_repo_name(HG_REPO)
+        Repository.unlock(r)
+        r.enable_locking = False
+        Session().add(r)
+        Session().commit()
+
+    def test_clone_hg_repo_by_admin(self):
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        assert 'requesting all changes' in stdout
+        assert 'adding changesets' in stdout
+        assert 'adding manifests' in stdout
+        assert 'adding file changes' in stdout
+
+        assert stderr == ''
+
+    def test_clone_git_repo_by_admin(self):
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        assert 'Cloning into' in stdout + stderr
+        assert stderr == '' or stdout == ''
+
+    def test_clone_wrong_credentials_hg(self):
+        clone_url = _construct_url(HG_REPO, passwd='bad!')
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        assert 'abort: authorization failed' in stderr
+
+    def test_clone_wrong_credentials_git(self):
+        clone_url = _construct_url(GIT_REPO, passwd='bad!')
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        assert 'fatal: Authentication failed' in stderr
+
+    def test_clone_git_dir_as_hg(self):
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_clone_hg_repo_as_git(self):
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        assert 'not found' in stderr
+
+    def test_clone_non_existing_path_hg(self):
+        clone_url = _construct_url('trololo')
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_clone_non_existing_path_git(self):
+        clone_url = _construct_url('trololo')
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        assert 'not found' in stderr
+
+    def test_push_new_file_hg(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST)
+
+        assert 'pushing to' in stdout
+        assert 'Repository size' in stdout
+        assert 'Last revision is now' in stdout
+
+    def test_push_new_file_git(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        # commit some stuff into this repo
+        stdout, stderr = _add_files_and_push('git', DEST)
+
+        print [(x.repo_full_path,x.repo_path) for x in Repository.get_all()]
+        _check_proper_git_push(stdout, stderr)
+
+    def test_push_invalidates_cache_hg(self):
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==HG_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(HG_REPO, HG_REPO)
+
+        key.cache_active = True
+        Session().add(key)
+        Session().commit()
+
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST, files_no=1)
+
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==HG_REPO).one()
+        self.assertEqual(key.cache_active, False)
+
+    def test_push_invalidates_cache_git(self):
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==GIT_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(GIT_REPO, GIT_REPO)
+
+        key.cache_active = True
+        Session().add(key)
+        Session().commit()
+
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        # commit some stuff into this repo
+        stdout, stderr = _add_files_and_push('git', DEST, files_no=1)
+        _check_proper_git_push(stdout, stderr)
+
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               ==GIT_REPO).one()
+        print CacheInvalidation.get_all()
+        self.assertEqual(key.cache_active, False)
+
+    def test_push_wrong_credentials_hg(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST, user='bad',
+                                             passwd='name')
+
+        assert 'abort: authorization failed' in stderr
+
+    def test_push_wrong_credentials_git(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('git', DEST, user='bad',
+                                             passwd='name')
+
+        assert 'fatal: Authentication failed' in stderr
+
+    def test_push_back_to_wrong_url_hg(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('hg', DEST,
+                                    clone_url='http://127.0.0.1:5000/tmp',)
+
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_push_back_to_wrong_url_git(self):
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        stdout, stderr = _add_files_and_push('git', DEST,
+                                    clone_url='http://127.0.0.1:5000/tmp',)
+
+        assert 'not found' in stderr
+
+    def test_clone_and_create_lock_hg(self):
+        # enable locking
+        r = Repository.get_by_repo_name(HG_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        # clone
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        #check if lock was made
+        r = Repository.get_by_repo_name(HG_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+    def test_clone_and_create_lock_git(self):
+        # enable locking
+        r = Repository.get_by_repo_name(GIT_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        # clone
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #check if lock was made
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+    def test_clone_after_repo_was_locked_hg(self):
+        #lock repo
+        r = Repository.get_by_repo_name(HG_REPO)
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+        #pull fails since repo is locked
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
+        assert msg in stderr
+
+    def test_clone_after_repo_was_locked_git(self):
+        #lock repo
+        r = Repository.get_by_repo_name(GIT_REPO)
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+        #pull fails since repo is locked
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+        msg = ("""The requested URL returned error: 423""")
+        assert msg in stderr
+
+    def test_push_on_locked_repo_by_other_user_hg(self):
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        #lock repo
+        r = Repository.get_by_repo_name(HG_REPO)
+        # let this user actually push !
+        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
+                                          perm='repository.write')
+        Session().commit()
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+
+        #push fails repo is locked by other user !
+        stdout, stderr = _add_files_and_push('hg', DEST,
+                                             user=TEST_USER_REGULAR_LOGIN,
+                                             passwd=TEST_USER_REGULAR_PASS)
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
+        assert msg in stderr
+
+    def test_push_on_locked_repo_by_other_user_git(self):
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #lock repo
+        r = Repository.get_by_repo_name(GIT_REPO)
+        # let this user actually push !
+        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
+                                          perm='repository.write')
+        Session().commit()
+        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
+
+        #push fails repo is locked by other user !
+        stdout, stderr = _add_files_and_push('git', DEST,
+                                             user=TEST_USER_REGULAR_LOGIN,
+                                             passwd=TEST_USER_REGULAR_PASS)
+        err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
+        assert err in stderr
+
+        #TODO: fix this somehow later on Git, Git is stupid and even if we throw
+        #back 423 to it, it makes ANOTHER request and we fail there with 405 :/
+
+        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
+                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
+        #msg = "405 Method Not Allowed"
+        #assert msg in stderr
+
+    def test_push_unlocks_repository_hg(self):
+        # enable locking
+        r = Repository.get_by_repo_name(HG_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(HG_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        #check for lock repo after clone
+        r = Repository.get_by_repo_name(HG_REPO)
+        uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+        assert r.locked[0] == uid
+
+        #push is ok and repo is now unlocked
+        stdout, stderr = _add_files_and_push('hg', DEST)
+        assert ('remote: Released lock on repo `%s`' % HG_REPO) in stdout
+        #we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(HG_REPO)
+        assert r.locked == [None, None]
+
+    #TODO: fix me ! somehow during tests hooks don't get called on Git
+    def test_push_unlocks_repository_git(self):
+        # enable locking
+        r = Repository.get_by_repo_name(GIT_REPO)
+        r.enable_locking = True
+        Session().add(r)
+        Session().commit()
+        #clone some temp
+        DEST = _get_tmp_dir()
+        clone_url = _construct_url(GIT_REPO, dest=DEST)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        #check for lock repo after clone
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
+
+        #push is ok and repo is now unlocked
+        stdout, stderr = _add_files_and_push('git', DEST)
+        _check_proper_git_push(stdout, stderr)
+
+        #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout
+        #we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(GIT_REPO)
+        assert r.locked == [None, None]
+
+    def test_ip_restriction_hg(self):
+        user_model = UserModel()
+        try:
+            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            Session().commit()
+            clone_url = _construct_url(HG_REPO)
+            stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+            assert 'abort: HTTP Error 403: Forbidden' in stderr
+        finally:
+            #release IP restrictions
+            for ip in UserIpMap.getAll():
+                UserIpMap.delete(ip.ip_id)
+            Session().commit()
+
+        time.sleep(2)
+        clone_url = _construct_url(HG_REPO)
+        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+        assert 'requesting all changes' in stdout
+        assert 'adding changesets' in stdout
+        assert 'adding manifests' in stdout
+        assert 'adding file changes' in stdout
+
+        assert stderr == ''
+
+    def test_ip_restriction_git(self):
+        user_model = UserModel()
+        try:
+            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            Session().commit()
+            clone_url = _construct_url(GIT_REPO)
+            stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+            msg = ("""The requested URL returned error: 403""")
+            assert msg in stderr
+        finally:
+            #release IP restrictions
+            for ip in UserIpMap.getAll():
+                UserIpMap.delete(ip.ip_id)
+            Session().commit()
+
+        time.sleep(2)
+        clone_url = _construct_url(GIT_REPO)
+        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+        assert 'Cloning into' in stdout + stderr
+        assert stderr == '' or stdout == ''
--- a/kallithea/tests/other/test_libs.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/other/test_libs.py	Thu May 07 13:30:17 2015 +0200
@@ -37,9 +37,9 @@
 TEST_URLS = [
     ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
     ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
      '%s://127.0.0.1:8080' % proto),
@@ -54,9 +54,9 @@
 TEST_URLS += [
     ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
-    ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
+    ('%s://username:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
      '%s://127.0.0.1' % proto),
     ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
      '%s://127.0.0.1:8080' % proto),
@@ -105,16 +105,16 @@
     def test_mention_extractor(self):
         from kallithea.lib.utils2 import extract_mentioned_users
         sample = (
-            "@first hi there @marcink here's my email marcin@email.com "
+            "@first hi there @world here's my email username@email.com "
             "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
-            "@MARCIN    @maRCiN @2one_more22 @john please see this http://org.pl "
+            "@UPPER    @cAmEL @2one_more22 @john please see this http://org.pl "
             "@marian.user just do it @marco-polo and next extract @marco_polo "
             "user.dot  hej ! not-needed maril@domain.org"
         )
 
         s = sorted([
-            '2one_more22', 'first', 'marcink', 'lukaszb', 'one', 'one_more22', 'MARCIN', 'maRCiN', 'john',
-            'marian.user', 'marco-polo', 'marco_polo'], key=lambda k: k.lower())
+            '2one_more22', 'first', 'lukaszb', 'one', 'one_more22', 'UPPER', 'cAmEL', 'john',
+            'marian.user', 'marco-polo', 'marco_polo', 'world'], key=lambda k: k.lower())
         self.assertEqual(s, extract_mentioned_users(sample))
 
     @parameterized.expand([
@@ -251,23 +251,23 @@
 
     @parameterized.expand([
         (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
-        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'username'}, '', 'http://username@vps1:8000/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/prefix', 'http://vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/prefix', 'http://user@vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'username'}, '/prefix', 'http://username@vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/prefix/', 'http://user@vps1:8000/prefix/group/repo1'),
+        (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'username'}, '/prefix/', 'http://username@vps1:8000/prefix/group/repo1'),
         ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
-        ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
-        ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
-        ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
-        ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
+        ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'username'}, '', 'http://username@vps1:8000/_23'),
+        ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'username'}, '', 'http://username@vps1:8000/_23'),
+        ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'username'}, '', 'http://vps1:8000/_23'),
+        ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'username'}, '', 'https://username@proxy1.server.com/group/repo1'),
         ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
-        ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
+        ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'username'}, '', 'https://proxy1.server.com/username/group/repo1'),
     ])
     def test_clone_url_generator(self, tmpl, repo_name, overrides, prefix, expected):
         from kallithea.lib.utils2 import get_clone_url
-        clone_url = get_clone_url(uri_tmpl=tmpl, qualifed_home_url='http://vps1:8000'+prefix,
+        clone_url = get_clone_url(uri_tmpl=tmpl, qualified_home_url='http://vps1:8000'+prefix,
                                   repo_name=repo_name, repo_id=23, **overrides)
         self.assertEqual(clone_url, expected)
 
@@ -366,7 +366,7 @@
       ("/_21/foobar", '21'),
       ("_21/121", '21'),
       ("/_21/_12", '21'),
-      ("_21/rc/foo", '21'),
+      ("_21/prefix/foo", '21'),
     ])
     def test_get_repo_by_id(self, test, expected):
         from kallithea.lib.utils import _extract_id_from_repo_name
--- a/kallithea/tests/other/test_vcs_operations.py	Thu May 07 13:29:49 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,533 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.tests.test_scm_operations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Test suite for making push/pull operations.
-Run using after doing paster serve test.ini::
- KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 nosetests kallithea/tests/other/test_vcs_operations.py
-
-You must have git > 1.8.1 for tests to work fine
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Dec 30, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-import tempfile
-import time
-from os.path import join as jn
-
-from tempfile import _RandomNameSequence
-from subprocess import Popen, PIPE
-
-from kallithea.tests import *
-from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation
-from kallithea.model.meta import Session
-from kallithea.model.repo import RepoModel
-from kallithea.model.user import UserModel
-
-DEBUG = True
-HOST = '127.0.0.1:5000'  # test host
-
-
-class Command(object):
-
-    def __init__(self, cwd):
-        self.cwd = cwd
-
-    def execute(self, cmd, *args):
-        """
-        Runs command on the system with given ``args``.
-        """
-
-        command = cmd + ' ' + ' '.join(args)
-        if DEBUG:
-            print '*** CMD %s ***' % command
-        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
-        stdout, stderr = p.communicate()
-        if DEBUG:
-            print stdout, stderr
-        return stdout, stderr
-
-
-def _get_tmp_dir():
-    return tempfile.mkdtemp(prefix='rc_integration_test')
-
-
-def _construct_url(repo, dest=None, **kwargs):
-    if dest is None:
-        #make temp clone
-        dest = _get_tmp_dir()
-    params = {
-        'user': TEST_USER_ADMIN_LOGIN,
-        'passwd': TEST_USER_ADMIN_PASS,
-        'host': HOST,
-        'cloned_repo': repo,
-        'dest': dest
-    }
-    params.update(**kwargs)
-    if params['user'] and params['passwd']:
-        _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s %(dest)s' % params
-    else:
-        _url = 'http://(host)s/%(cloned_repo)s %(dest)s' % params
-    return _url
-
-
-def _add_files_and_push(vcs, DEST, **kwargs):
-    """
-    Generate some files, add it to DEST repo and push back
-    vcs is git or hg and defines what VCS we want to make those files for
-
-    :param vcs:
-    :param DEST:
-    """
-    # commit some stuff into this repo
-    cwd = path = jn(DEST)
-    #added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
-    added_file = jn(path, '%ssetup.py' % _RandomNameSequence().next())
-    Command(cwd).execute('touch %s' % added_file)
-    Command(cwd).execute('%s add %s' % (vcs, added_file))
-
-    for i in xrange(kwargs.get('files_no', 3)):
-        cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
-        Command(cwd).execute(cmd)
-        author_str = 'Marcin Kuźminski <me@email.com>'
-        if vcs == 'hg':
-            cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
-                i, author_str, added_file
-            )
-        elif vcs == 'git':
-            cmd = """EMAIL="me@email.com" git commit -m 'commited new %s' --author '%s' %s """ % (
-                i, author_str, added_file
-            )
-        Command(cwd).execute(cmd)
-
-    # PUSH it back
-    _REPO = None
-    if vcs == 'hg':
-        _REPO = HG_REPO
-    elif vcs == 'git':
-        _REPO = GIT_REPO
-
-    kwargs['dest'] = ''
-    clone_url = _construct_url(_REPO, **kwargs)
-    if 'clone_url' in kwargs:
-        clone_url = kwargs['clone_url']
-    stdout = stderr = None
-    if vcs == 'hg':
-        stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url)
-    elif vcs == 'git':
-        stdout, stderr = Command(cwd).execute('git push --verbose', clone_url + " master")
-
-    return stdout, stderr
-
-
-def set_anonymous_access(enable=True):
-    user = User.get_by_username(User.DEFAULT_USER)
-    user.active = enable
-    Session().add(user)
-    Session().commit()
-    print '\tanonymous access is now:', enable
-    if enable != User.get_by_username(User.DEFAULT_USER).active:
-        raise Exception('Cannot set anonymous access')
-
-
-#==============================================================================
-# TESTS
-#==============================================================================
-
-
-def _check_proper_git_push(stdout, stderr):
-    #WTF Git stderr is output ?!
-    assert 'fatal' not in stderr
-    assert 'rejected' not in stderr
-    assert 'Pushing to' in stderr
-    assert 'master -> master' in stderr
-
-
-class TestVCSOperations(BaseTestCase):
-
-    @classmethod
-    def setup_class(cls):
-        #DISABLE ANONYMOUS ACCESS
-        set_anonymous_access(False)
-
-    def setUp(self):
-        r = Repository.get_by_repo_name(GIT_REPO)
-        Repository.unlock(r)
-        r.enable_locking = False
-        Session().add(r)
-        Session().commit()
-
-        r = Repository.get_by_repo_name(HG_REPO)
-        Repository.unlock(r)
-        r.enable_locking = False
-        Session().add(r)
-        Session().commit()
-
-    def test_clone_hg_repo_by_admin(self):
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        assert 'requesting all changes' in stdout
-        assert 'adding changesets' in stdout
-        assert 'adding manifests' in stdout
-        assert 'adding file changes' in stdout
-
-        assert stderr == ''
-
-    def test_clone_git_repo_by_admin(self):
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        assert 'Cloning into' in stdout
-        assert stderr == ''
-
-    def test_clone_wrong_credentials_hg(self):
-        clone_url = _construct_url(HG_REPO, passwd='bad!')
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        assert 'abort: authorization failed' in stderr
-
-    def test_clone_wrong_credentials_git(self):
-        clone_url = _construct_url(GIT_REPO, passwd='bad!')
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'fatal: Authentication failed' in stderr
-
-    def test_clone_git_dir_as_hg(self):
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        assert 'HTTP Error 404: Not Found' in stderr
-
-    def test_clone_hg_repo_as_git(self):
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'not found' in stderr
-
-    def test_clone_non_existing_path_hg(self):
-        clone_url = _construct_url('trololo')
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        assert 'HTTP Error 404: Not Found' in stderr
-
-    def test_clone_non_existing_path_git(self):
-        clone_url = _construct_url('trololo')
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        assert 'not found' in stderr
-
-    def test_push_new_file_hg(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST)
-
-        assert 'pushing to' in stdout
-        assert 'Repository size' in stdout
-        assert 'Last revision is now' in stdout
-
-    def test_push_new_file_git(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        # commit some stuff into this repo
-        stdout, stderr = _add_files_and_push('git', DEST)
-
-        print [(x.repo_full_path,x.repo_path) for x in Repository.get_all()]
-        _check_proper_git_push(stdout, stderr)
-
-    def test_push_invalidates_cache_hg(self):
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==HG_REPO).scalar()
-        if not key:
-            key = CacheInvalidation(HG_REPO, HG_REPO)
-
-        key.cache_active = True
-        Session().add(key)
-        Session().commit()
-
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST, files_no=1)
-
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==HG_REPO).one()
-        self.assertEqual(key.cache_active, False)
-
-    def test_push_invalidates_cache_git(self):
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==GIT_REPO).scalar()
-        if not key:
-            key = CacheInvalidation(GIT_REPO, GIT_REPO)
-
-        key.cache_active = True
-        Session().add(key)
-        Session().commit()
-
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        # commit some stuff into this repo
-        stdout, stderr = _add_files_and_push('git', DEST, files_no=1)
-        _check_proper_git_push(stdout, stderr)
-
-        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
-                                               ==GIT_REPO).one()
-        print CacheInvalidation.get_all()
-        self.assertEqual(key.cache_active, False)
-
-    def test_push_wrong_credentials_hg(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST, user='bad',
-                                             passwd='name')
-
-        assert 'abort: authorization failed' in stderr
-
-    def test_push_wrong_credentials_git(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('git', DEST, user='bad',
-                                             passwd='name')
-
-        assert 'fatal: Authentication failed' in stderr
-
-    def test_push_back_to_wrong_url_hg(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('hg', DEST,
-                                    clone_url='http://127.0.0.1:5000/tmp',)
-
-        assert 'HTTP Error 404: Not Found' in stderr
-
-    def test_push_back_to_wrong_url_git(self):
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        stdout, stderr = _add_files_and_push('git', DEST,
-                                    clone_url='http://127.0.0.1:5000/tmp',)
-
-        assert 'not found' in stderr
-
-    def test_clone_and_create_lock_hg(self):
-        # enable locking
-        r = Repository.get_by_repo_name(HG_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        # clone
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        #check if lock was made
-        r = Repository.get_by_repo_name(HG_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-
-    def test_clone_and_create_lock_git(self):
-        # enable locking
-        r = Repository.get_by_repo_name(GIT_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        # clone
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        #check if lock was made
-        r = Repository.get_by_repo_name(GIT_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-
-    def test_clone_after_repo_was_locked_hg(self):
-        #lock repo
-        r = Repository.get_by_repo_name(HG_REPO)
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-        #pull fails since repo is locked
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
-        assert msg in stderr
-
-    def test_clone_after_repo_was_locked_git(self):
-        #lock repo
-        r = Repository.get_by_repo_name(GIT_REPO)
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-        #pull fails since repo is locked
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-        msg = ("""The requested URL returned error: 423""")
-        assert msg in stderr
-
-    def test_push_on_locked_repo_by_other_user_hg(self):
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        #lock repo
-        r = Repository.get_by_repo_name(HG_REPO)
-        # let this user actually push !
-        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
-                                          perm='repository.write')
-        Session().commit()
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-
-        #push fails repo is locked by other user !
-        stdout, stderr = _add_files_and_push('hg', DEST,
-                                             user=TEST_USER_REGULAR_LOGIN,
-                                             passwd=TEST_USER_REGULAR_PASS)
-        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-                % (HG_REPO, TEST_USER_ADMIN_LOGIN))
-        assert msg in stderr
-
-    def test_push_on_locked_repo_by_other_user_git(self):
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        #lock repo
-        r = Repository.get_by_repo_name(GIT_REPO)
-        # let this user actually push !
-        RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
-                                          perm='repository.write')
-        Session().commit()
-        Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
-
-        #push fails repo is locked by other user !
-        stdout, stderr = _add_files_and_push('git', DEST,
-                                             user=TEST_USER_REGULAR_LOGIN,
-                                             passwd=TEST_USER_REGULAR_PASS)
-        err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
-        assert err in stderr
-
-        #TODO: fix this somehow later on Git, Git is stupid and even if we throw
-        #back 423 to it, it makes ANOTHER request and we fail there with 405 :/
-
-        msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
-                % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
-        #msg = "405 Method Not Allowed"
-        #assert msg in stderr
-
-    def test_push_unlocks_repository_hg(self):
-        # enable locking
-        r = Repository.get_by_repo_name(HG_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(HG_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        #check for lock repo after clone
-        r = Repository.get_by_repo_name(HG_REPO)
-        uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-        assert r.locked[0] == uid
-
-        #push is ok and repo is now unlocked
-        stdout, stderr = _add_files_and_push('hg', DEST)
-        assert ('remote: Released lock on repo `%s`' % HG_REPO) in stdout
-        #we need to cleanup the Session Here !
-        Session.remove()
-        r = Repository.get_by_repo_name(HG_REPO)
-        assert r.locked == [None, None]
-
-    #TODO: fix me ! somehow during tests hooks don't get called on Git
-    def test_push_unlocks_repository_git(self):
-        # enable locking
-        r = Repository.get_by_repo_name(GIT_REPO)
-        r.enable_locking = True
-        Session().add(r)
-        Session().commit()
-        #clone some temp
-        DEST = _get_tmp_dir()
-        clone_url = _construct_url(GIT_REPO, dest=DEST)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        #check for lock repo after clone
-        r = Repository.get_by_repo_name(GIT_REPO)
-        assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
-
-        #push is ok and repo is now unlocked
-        stdout, stderr = _add_files_and_push('git', DEST)
-        _check_proper_git_push(stdout, stderr)
-
-        #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout
-        #we need to cleanup the Session Here !
-        Session.remove()
-        r = Repository.get_by_repo_name(GIT_REPO)
-        assert r.locked == [None, None]
-
-    def test_ip_restriction_hg(self):
-        user_model = UserModel()
-        try:
-            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
-            Session().commit()
-            clone_url = _construct_url(HG_REPO)
-            stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-            assert 'abort: HTTP Error 403: Forbidden' in stderr
-        finally:
-            #release IP restrictions
-            for ip in UserIpMap.getAll():
-                UserIpMap.delete(ip.ip_id)
-            Session().commit()
-
-        time.sleep(2)
-        clone_url = _construct_url(HG_REPO)
-        stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
-
-        assert 'requesting all changes' in stdout
-        assert 'adding changesets' in stdout
-        assert 'adding manifests' in stdout
-        assert 'adding file changes' in stdout
-
-        assert stderr == ''
-
-    def test_ip_restriction_git(self):
-        user_model = UserModel()
-        try:
-            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
-            Session().commit()
-            clone_url = _construct_url(GIT_REPO)
-            stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-            msg = ("""The requested URL returned error: 403""")
-            assert msg in stderr
-        finally:
-            #release IP restrictions
-            for ip in UserIpMap.getAll():
-                UserIpMap.delete(ip.ip_id)
-            Session().commit()
-
-        time.sleep(2)
-        clone_url = _construct_url(GIT_REPO)
-        stdout, stderr = Command('/tmp').execute('git clone', clone_url)
-
-        assert 'Cloning into' in stdout
-        assert stderr == ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/parameterized.py	Thu May 07 13:30:17 2015 +0200
@@ -0,0 +1,241 @@
+import re
+import new
+import inspect
+import logging
+import logging.handlers
+from functools import wraps
+
+from unittest import TestCase
+
+
+def skip_test(func):
+    try:
+        from nose.tools import nottest
+    except ImportError:
+        pass
+    else:
+        func = nottest(func)
+
+    try:
+        import pytest
+    except ImportError:
+        pass
+    else:
+        func = pytest.mark.skipIf(True, func)
+
+    return func
+
+
+def _terrible_magic_get_defining_classes():
+    """ Returns the set of parent classes of the class currently being defined.
+        Will likely only work if called from the ``parameterized`` decorator.
+        This function is entirely @brandon_rhodes's fault, as he suggested
+        the implementation: http://stackoverflow.com/a/8793684/71522
+        """
+    stack = inspect.stack()
+    if len(stack) <= 4:
+        return []
+    frame = stack[3]
+    code_context = frame[4][0].strip()
+    if not code_context.startswith("class "):
+        return []
+    _, parents = code_context.split("(", 1)
+    parents, _ = parents.rsplit(")", 1)
+    return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
+
+
+def parameterized(input):
+    """ Parameterize a test case:
+        >>> add1_tests = [(1, 2), (2, 3)]
+        >>> class TestFoo(object):
+        ...     @parameterized(add1_tests)
+        ...     def test_add1(self, input, expected):
+        ...         assert_equal(add1(input), expected)
+        >>> @parameterized(add1_tests)
+        ... def test_add1(input, expected):
+        ...     assert_equal(add1(input), expected)
+        >>>
+        """
+
+    if not hasattr(input, "__iter__"):
+        raise ValueError("expected iterable input; got %r" % (input,))
+
+    def parameterized_helper(f):
+        attached_instance_method = [False]
+
+        parent_classes = _terrible_magic_get_defining_classes()
+        if any(issubclass(cls, TestCase) for cls in parent_classes):
+            raise Exception("Warning: '@parameterized' tests won't work "
+                            "inside subclasses of 'TestCase' - use "
+                            "'@parameterized.expand' instead")
+
+        @wraps(f)
+        def parameterized_helper_method(self=None):
+            if self is not None and not attached_instance_method[0]:
+                # confusingly, we need to create a named instance method and
+                # attach that to the class...
+                cls = self.__class__
+                im_f = new.instancemethod(f, None, cls)
+                setattr(cls, f.__name__, im_f)
+                attached_instance_method[0] = True
+            for args in input:
+                if isinstance(args, basestring):
+                    args = [args]
+                # ... then pull that named instance method off, turning it into
+                # a bound method ...
+                if self is not None:
+                    args = [getattr(self, f.__name__)] + list(args)
+                else:
+                    args = [f] + list(args)
+                # ... then yield that as a tuple. If those steps aren't
+                # followed precicely, Nose gets upset and doesn't run the test
+                # or doesn't run setup methods.
+                yield tuple(args)
+
+        f.__name__ = "_helper_for_%s" % (f.__name__,)
+        parameterized_helper_method.parameterized_input = input
+        parameterized_helper_method.parameterized_func = f
+        return parameterized_helper_method
+
+    return parameterized_helper
+
+
+def to_safe_name(s):
+    return re.sub("[^a-zA-Z0-9_]", "", s)
+
+
+def parameterized_expand_helper(func_name, func, args):
+    def parameterized_expand_helper_helper(self=()):
+        if self != ():
+            self = (self,)
+        return func(*(self + args))
+    parameterized_expand_helper_helper.__name__ = str(func_name)
+    return parameterized_expand_helper_helper
+
+
+def parameterized_expand(input):
+    """ A "brute force" method of parameterizing test cases. Creates new test
+        cases and injects them into the namespace that the wrapped function
+        is being defined in. Useful for parameterizing tests in subclasses
+        of 'UnitTest', where Nose test generators don't work.
+
+        >>> @parameterized.expand([("foo", 1, 2)])
+        ... def test_add1(name, input, expected):
+        ...     actual = add1(input)
+        ...     assert_equal(actual, expected)
+        ...
+        >>> locals()
+        ... 'test_add1_foo_0': <function ...> ...
+        >>>
+        """
+
+    def parameterized_expand_wrapper(f):
+        stack = inspect.stack()
+        frame = stack[1]
+        frame_locals = frame[0].f_locals
+
+        base_name = f.__name__
+        for num, args in enumerate(input):
+            name_suffix = "_%s" % (num,)
+            if len(args) > 0 and isinstance(args[0], basestring):
+                name_suffix += "_" + to_safe_name(args[0])
+            name = base_name + name_suffix
+            new_func = parameterized_expand_helper(name, f, args)
+            frame_locals[name] = new_func
+        return skip_test(f)
+    return parameterized_expand_wrapper
+
+parameterized.expand = parameterized_expand
+
+
+def assert_contains(haystack, needle):
+    if needle not in haystack:
+        raise AssertionError("%r not in %r" % (needle, haystack))
+
+
+def assert_not_contains(haystack, needle):
+    if needle in haystack:
+        raise AssertionError("%r in %r" % (needle, haystack))
+
+
+def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
+    try:
+        func()
+    except exc_type, e:
+        if str_contains is not None and str_contains not in str(e):
+            raise AssertionError("%s raised, but %r does not contain %r"
+                                 % (exc_type, str(e), str_contains))
+        if repr_contains is not None and repr_contains not in repr(e):
+            raise AssertionError("%s raised, but %r does not contain %r"
+                                 % (exc_type, repr(e), repr_contains))
+        return e
+    else:
+        raise AssertionError("%s not raised" % (exc_type,))
+
+
+log_handler = None
+
+
+def setup_logging():
+    """ Configures a log handler which will capure log messages during a test.
+        The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
+        used to make assertions about these logged messages.
+
+        For example::
+
+            from ensi_common.testing import (
+                setup_logging, teardown_logging, assert_no_errors_logged,
+                assert_logged,
+            )
+
+            class TestWidget(object):
+                def setup(self):
+                    setup_logging()
+
+                def teardown(self):
+                    assert_no_errors_logged()
+                    teardown_logging()
+
+                def test_that_will_fail(self):
+                    log.warning("this warning message will trigger a failure")
+
+                def test_that_will_pass(self):
+                    log.info("but info messages are ok")
+                    assert_logged("info messages are ok")
+        """
+
+    global log_handler
+    if log_handler is not None:
+        logging.getLogger().removeHandler(log_handler)
+    log_handler = logging.handlers.BufferingHandler(1000)
+    formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
+    log_handler.setFormatter(formatter)
+    logging.getLogger().addHandler(log_handler)
+
+
+def teardown_logging():
+    global log_handler
+    if log_handler is not None:
+        logging.getLogger().removeHandler(log_handler)
+        log_handler = None
+
+
+def logged_messages():
+    assert log_handler, "setup_logging not called"
+    return [(log_handler.format(record), record) for record in log_handler.buffer]
+
+
+def assert_no_errors_logged():
+    for _, record in logged_messages():
+        if record.levelno >= logging.WARNING:
+            # Assume that the nose log capture plugin is being used, so it will
+            # show the exception.
+            raise AssertionError("an unexpected error was logged")
+
+
+def assert_logged(expected_msg_contents):
+    for msg, _ in logged_messages():
+        if expected_msg_contents in msg:
+            return
+    raise AssertionError("no logged message contains %r"
+                         % (expected_msg_contents,))
--- a/kallithea/tests/scripts/create_rc.sh	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/scripts/create_rc.sh	Thu May 07 13:30:17 2015 +0200
@@ -1,10 +1,10 @@
 #!/bin/sh
 psql -U postgres -h localhost -c 'drop database if exists kallithea;'
 psql -U postgres -h localhost -c 'create database kallithea;'
-paster setup-db rc.ini --force-yes --user=username --password=qweqwe --email=username@example.com --repos=/home/username/repos --no-public-access
+paster setup-db server.ini --force-yes --user=username --password=qweqwe --email=username@example.com --repos=/home/username/repos --no-public-access
 API_KEY=`psql -R " " -A -U postgres -h localhost -c "select api_key from users where admin=TRUE" -d kallithea | awk '{print $2}'`
 echo "run those after running server"
-paster serve rc.ini --pid-file=rc.pid --daemon
+paster serve server.ini --pid-file=server.pid --daemon
 sleep 3
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@example.com
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@example.com
@@ -13,5 +13,5 @@
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo1
 kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo2
 echo "killing server"
-kill `cat rc.pid`
-rm rc.pid
+kill `cat server.pid`
+rm server.pid
--- a/kallithea/tests/scripts/test_concurency.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/scripts/test_concurency.py	Thu May 07 13:30:17 2015 +0200
@@ -49,14 +49,14 @@
 from kallithea.config.environment import load_environment
 
 rel_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
-conf = appconfig('config:rc.ini', relative_to=rel_path)
+conf = appconfig('config:development.ini', relative_to=rel_path)
 load_environment(conf.global_conf, conf.local_conf)
 
 add_cache(conf)
 
 USER = 'test_admin'
 PASS = 'test12'
-HOST = 'rc.local'
+HOST = 'server.local'
 METHOD = 'pull'
 DEBUG = True
 log = logging.getLogger(__name__)
--- a/kallithea/tests/scripts/test_crawler.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/scripts/test_crawler.py	Thu May 07 13:30:17 2015 +0200
@@ -60,7 +60,7 @@
 
 print 'Crawling @ %s' % BASE_URI
 BASE_URI += '%s'
-PROJECT_PATH = jn('/', 'home', 'marcink', 'repos')
+PROJECT_PATH = jn('/', 'home', 'username', 'repos')
 PROJECTS = [
     #'linux-magx-pbranch',
     'CPython',
--- a/kallithea/tests/vcs/base.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/base.py	Thu May 07 13:30:17 2015 +0200
@@ -13,7 +13,7 @@
 from kallithea.lib.vcs.nodes import FileNode
 
 
-class BackendTestMixin(object):
+class _BackendTestMixin(object):
     """
     This is a backend independent test case class which should be created
     with ``type`` method.
@@ -103,7 +103,7 @@
         'backend_alias': alias,
     }
     cls_name = ''.join(('%s base backend test' % alias).title().split())
-    bases = (BackendTestMixin, unittest.TestCase)
+    bases = (_BackendTestMixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
 
--- a/kallithea/tests/vcs/test_archives.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_archives.py	Thu May 07 13:30:17 2015 +0200
@@ -6,14 +6,14 @@
 import datetime
 import tempfile
 import StringIO
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.exceptions import VCSError
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class ArchivesTestCaseMixin(BackendTestMixin):
+class ArchivesTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_branches.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_branches.py	Thu May 07 13:30:17 2015 +0200
@@ -5,11 +5,11 @@
 from kallithea.lib.vcs.utils.compat import unittest
 from kallithea.lib.vcs.nodes import FileNode
 
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 
 
-class BranchesTestCaseMixin(BackendTestMixin):
+class BranchesTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_changesets.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_changesets.py	Thu May 07 13:30:17 2015 +0200
@@ -4,7 +4,7 @@
 import time
 import datetime
 from kallithea.lib import vcs
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 
 from kallithea.lib.vcs.backends.base import BaseChangeset
@@ -50,7 +50,7 @@
             'removed': [],
         })
 
-class ChangesetsWithCommitsTestCaseixin(BackendTestMixin):
+class _ChangesetsWithCommitsTestCaseixin(_BackendTestMixin):
     recreate_repo_per_test = True
 
     @classmethod
@@ -146,7 +146,7 @@
             self.assertEqual([sha], self.repo.get_changeset(test_rev).children)
 
 
-class ChangesetsTestCaseMixin(BackendTestMixin):
+class _ChangesetsTestCaseMixin(_BackendTestMixin):
     recreate_repo_per_test = False
 
     @classmethod
@@ -301,7 +301,7 @@
             list(self.repo.get_changesets(start=last-1, end=0))
 
 
-class ChangesetsChangesTestCaseMixin(BackendTestMixin):
+class _ChangesetsChangesTestCaseMixin(_BackendTestMixin):
     recreate_repo_per_test = False
 
     @classmethod
@@ -373,17 +373,17 @@
     }
     # tests with additional commits
     cls_name = ''.join(('%s changesets with commits test' % alias).title().split())
-    bases = (ChangesetsWithCommitsTestCaseixin, unittest.TestCase)
+    bases = (_ChangesetsWithCommitsTestCaseixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
     # tests without additional commits
     cls_name = ''.join(('%s changesets test' % alias).title().split())
-    bases = (ChangesetsTestCaseMixin, unittest.TestCase)
+    bases = (_ChangesetsTestCaseMixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
     # tests changes
     cls_name = ''.join(('%s changesets changes test' % alias).title().split())
-    bases = (ChangesetsChangesTestCaseMixin, unittest.TestCase)
+    bases = (_ChangesetsChangesTestCaseMixin, unittest.TestCase)
     globals()[cls_name] = type(cls_name, bases, attrs)
 
 
--- a/kallithea/tests/vcs/test_getitem.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_getitem.py	Thu May 07 13:30:17 2015 +0200
@@ -1,13 +1,13 @@
 from __future__ import with_statement
 
 import datetime
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class GetitemTestCaseMixin(BackendTestMixin):
+class GetitemTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_getslice.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_getslice.py	Thu May 07 13:30:17 2015 +0200
@@ -1,13 +1,13 @@
 from __future__ import with_statement
 
 import datetime
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class GetsliceTestCaseMixin(BackendTestMixin):
+class GetsliceTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_git.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_git.py	Thu May 07 13:30:17 2015 +0200
@@ -8,7 +8,7 @@
 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
 from kallithea.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState
 from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
 
 
@@ -620,7 +620,7 @@
             changeset.added
 
 
-class GitSpecificWithRepoTest(BackendTestMixin, unittest.TestCase):
+class GitSpecificWithRepoTest(_BackendTestMixin, unittest.TestCase):
     backend_alias = 'git'
 
     @classmethod
@@ -688,7 +688,7 @@
             % (3, self.repo._get_revision(0), self.repo._get_revision(1)))
 
 
-class GitRegressionTest(BackendTestMixin, unittest.TestCase):
+class GitRegressionTest(_BackendTestMixin, unittest.TestCase):
     backend_alias = 'git'
 
     @classmethod
--- a/kallithea/tests/vcs/test_repository.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_repository.py	Thu May 07 13:30:17 2015 +0200
@@ -1,6 +1,6 @@
 from __future__ import with_statement
 import datetime
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.tests.vcs.conf import TEST_USER_CONFIG_FILE
 from kallithea.lib.vcs.nodes import FileNode
@@ -8,7 +8,7 @@
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
 
 
-class RepositoryBaseTest(BackendTestMixin):
+class RepositoryBaseTest(_BackendTestMixin):
     recreate_repo_per_test = False
 
     @classmethod
@@ -46,7 +46,7 @@
         self.assertTrue(self.repo != dummy())
 
 
-class RepositoryGetDiffTest(BackendTestMixin):
+class RepositoryGetDiffTest(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/kallithea/tests/vcs/test_tags.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_tags.py	Thu May 07 13:30:17 2015 +0200
@@ -1,13 +1,13 @@
 from __future__ import with_statement
 
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 from kallithea.lib.vcs.exceptions import TagAlreadyExistError
 from kallithea.lib.vcs.exceptions import TagDoesNotExistError
 from kallithea.lib.vcs.utils.compat import unittest
 
 
-class TagsTestCaseMixin(BackendTestMixin):
+class TagsTestCaseMixin(_BackendTestMixin):
 
     def test_new_tag(self):
         tip = self.repo.get_changeset()
--- a/kallithea/tests/vcs/test_utils.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_utils.py	Thu May 07 13:30:17 2015 +0200
@@ -185,14 +185,14 @@
 
 
 class TestAuthorExtractors(unittest.TestCase):
-    TEST_AUTHORS = [('Marcin Kuzminski <marcin@python-works.com>',
-                    ('Marcin Kuzminski', 'marcin@python-works.com')),
-                  ('Marcin Kuzminski Spaces < marcin@python-works.com >',
-                    ('Marcin Kuzminski Spaces', 'marcin@python-works.com')),
-                  ('Marcin Kuzminski <marcin.kuzminski@python-works.com>',
-                    ('Marcin Kuzminski', 'marcin.kuzminski@python-works.com')),
-                  ('mrf RFC_SPEC <marcin+kuzminski@python-works.com>',
-                    ('mrf RFC_SPEC', 'marcin+kuzminski@python-works.com')),
+    TEST_AUTHORS = [("Username Last'o'Name <username@python-works.com>",
+                    ("Username Last'o'Name", "username@python-works.com")),
+                  ("Username Last'o'Name Spaces < username@python-works.com >",
+                    ("Username Last'o'Name Spaces", "username@python-works.com")),
+                  ("Username Last'o'Name <username.lastname@python-works.com>",
+                    ("Username Last'o'Name", "username.lastname@python-works.com")),
+                  ('mrf RFC_SPEC <username+lastname@python-works.com>',
+                    ('mrf RFC_SPEC', 'username+lastname@python-works.com')),
                   ('username <user@email.com>',
                     ('username', 'user@email.com')),
                   ('username <user@email.com',
--- a/kallithea/tests/vcs/test_workdirs.py	Thu May 07 13:29:49 2015 +0200
+++ b/kallithea/tests/vcs/test_workdirs.py	Thu May 07 13:30:17 2015 +0200
@@ -3,11 +3,11 @@
 import datetime
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.base import BackendTestMixin
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import SCM_TESTS
 
 
-class WorkdirTestCaseMixin(BackendTestMixin):
+class WorkdirTestCaseMixin(_BackendTestMixin):
 
     @classmethod
     def _get_commits(cls):
--- a/production.ini	Thu May 07 13:29:49 2015 +0200
+++ b/production.ini	Thu May 07 13:30:17 2015 +0200
@@ -133,7 +133,7 @@
 host = 127.0.0.1
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- a/setup.cfg	Thu May 07 13:29:49 2015 +0200
+++ b/setup.cfg	Thu May 07 13:30:17 2015 +0200
@@ -10,6 +10,9 @@
 detailed-errors = 1
 nologcapture = 1
 
+[pytest]
+norecursedirs = .* *.egg kallithea/tests/scripts
+
 [compile_catalog]
 domain = kallithea
 directory = kallithea/i18n
--- a/test.ini	Thu May 07 13:29:49 2015 +0200
+++ b/test.ini	Thu May 07 13:30:17 2015 +0200
@@ -133,7 +133,7 @@
 host = 127.0.0.1
 port = 5000
 
-## prefix middleware for rc
+## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
 #prefix = /<your-prefix>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tox.ini	Thu May 07 13:30:17 2015 +0200
@@ -0,0 +1,12 @@
+[tox]
+envlist = py{26,27}-{pytest,nose}
+
+[testenv]
+setenv =
+    PYTHONHASHSEED = 0
+deps =
+    nose: nose
+    pytest: pytest
+commands =
+    nose: nosetests {posargs}
+    pytest: py.test {posargs}